관계, Networking 그리고 Programming

SWIFT - 앱 동작 시 터치 표현 보여주기

androbook 본캐 2017. 10. 30. 17:47

하앍하앍 드디어!!!!!! 굿캘린더의 IOS 버전을 리뷰에 올렸다. 현재 심사 대기 중. 아아 심장이 바운스 바운스!!!!!! 리뷰 통과했드아아아~~ 아하하하하하


from. giphy.com


주말에는 나와서 튜토리얼/미리보기 영상과 스냅샷들을 준비하느라 분주했음. 튜토리얼 영상을 만드느라 앱 동작 위에 터치를 보여주는 라이브러리를 찾아놨는데, Objective-C로 되어 있는 라이브러리인지라 swift로 옮겨서 사용했다. 앞으로도 계속 swift로 뭔가를 만들 텐데 매 번 설정 바꿔주고 하는게 귀찮을 듯 하여...혹시 필요한 사람이 있을런지 몰라 올려둠. 원본 라이브러리의 라이센스는 BSD-3-Clause. 음. 카피라이트를 명시하고 배포하라는데, swift로 바꾼 경우에는 어떻게 되는거지??? -_-;;; 일단 Objective-C 라이브러리의 카피라이트는 다음과 같음.


원본 Objective-C 라이브러리 사이트 : GitHub_link


Copyright (c) 2011-2017 Mapbox, Inc.


라이센스 문제는 너무나 복잡하므로 문제가 생긴다면 내리는 것으로...아래쪽 3개 swift 파일을 프로젝트에 포함 시킨 뒤, AppDelegate에서 아래와 같이 window를 바꿔 준다. 그러면 이후로 앱 동작 시 터치 포인트가 흰색 동그라미로 표현됨.


1
2
3
4
5
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
 
    var window: UIWindow? = FingerTips(frame: UIScreen.main.bounds)
 
cs



FingerTipView.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import UIKit
 
class FingerTipView: UIImageView {
    var timestamp:TimeInterval = Date().timeIntervalSince1970
    var shouldAutomaticallyRemoveAfterTimeout:Bool = false
    var fadingOut:Bool = false
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override init(image: UIImage?) {
        super.init(image: image)
    }
}
cs



FingerTipOverlayWindow.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import UIKit
 
class FingerTipOverlayWindow: UIWindow {
    
    var _rootViewController:UIViewController {
        for window in UIApplication.shared.windows {
            if self == window {
                continue
            }
            
            let realRootViewController = window.rootViewController
            if realRootViewController != nil {
                return realRootViewController!
            }
        }
        return super.rootViewController!
    }
}
cs



FingerTips.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
import UIKit
 
class FingerTips: UIWindow {
    
    var _touchImage:UIImage? = nil
    var touchAlpha:CGFloat = 0.5
    var fadeDuration:TimeInterval = 0.3
    var strokeColor:UIColor = UIColor.black
    var fillColor:UIColor = UIColor.white
    var alwaysShowTouches:Bool = true
    
    var _overlayWindow:UIWindow? = nil
    var active:Bool = false
    var fingerTipRemovalScheduled:Bool = false
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        myInit()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        myInit()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    func myInit() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.screenConnect(notification:)), name: NSNotification.Name.UIScreenDidConnect, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.screenDisConnect(notification:)), name: NSNotification.Name.UIScreenDidDisconnect, object: nil)
        
        updateFingertipsAreActive()
    }
    
    func screenConnect(notification:NSNotification)
    {
        updateFingertipsAreActive()
    }
    
    func screenDisConnect(notification:NSNotification)
    {
        updateFingertipsAreActive()
    }
    
    func anyScreenIsMirrored() -> Bool {
        if UIScreen.instancesRespond(to:#selector(getter: UIScreen.mirrored)) == false {
            return false
        }
        
        for screen in UIScreen.screens
        {
            if screen.mirrored != nil {
                return true
            }
        }
        
        return false
    }
 
    func updateFingertipsAreActive() {
        
        if alwaysShowTouches
        {
            self.active = true
        }
        else
        {
            self.active = self.anyScreenIsMirrored()
        }
    }
    
    func setAlwaysShowTouches(flag:Bool) {
        if alwaysShowTouches != flag
        {
            alwaysShowTouches = flag;
            updateFingertipsAreActive()
        }
    }
    
    override func sendEvent(_ event: UIEvent) {
        if self.active {
            let allTouches = event.allTouches
            
            for touch in allTouches!{
                
                switch (touch.phase)
                {
                case UITouchPhase.began,
                     UITouchPhase.moved,
                     UITouchPhase.stationary:
                    
                    var touchView = self.overlayWindow.viewWithTag(touch.hash) as? FingerTipView
                    
                    if (touch.phase != UITouchPhase.stationary && touchView != nil && (touchView?.fadingOut)!)
                    {
                        touchView?.removeFromSuperview()
                        touchView = nil
                    }
                    
                    if (touchView == nil && touch.phase != UITouchPhase.stationary)
                    {
                        touchView = FingerTipView(image: self.touchImage)
                        self.overlayWindow.addSubview(touchView!)
                    }
                    
                    if touchView?.fadingOut == false {
                        touchView?.alpha = self.touchAlpha;
                        touchView?.center = touch.location(in: self.overlayWindow)
                        touchView?.tag = touch.hash;
                        touchView?.timestamp = touch.timestamp;
                        touchView?.shouldAutomaticallyRemoveAfterTimeout = shouldAutomaticallyRemoveFingerTipForTouch(touch: touch)
                    }
                    break
                case UITouchPhase.ended,
                     UITouchPhase.cancelled:
                    removeFingerTipWithHash(hash: touch.hash, animated: true)
                    break
                }
            }
        }
        
        super.sendEvent(event)
        
        scheduleFingerTipRemoval()
    }
    
    func scheduleFingerTipRemoval() {
        if self.fingerTipRemovalScheduled {
            return
        }
        
        self.fingerTipRemovalScheduled = true
        self.perform( #selector(self.removeInactiveFingerTips), with: nil, afterDelay: 0.1)
    }
    
    func cancelScheduledFingerTipRemoval() {
        self.fingerTipRemovalScheduled = true
        NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.removeInactiveFingerTips), object: nil)
    }
    
    func removeInactiveFingerTips() {
        self.fingerTipRemovalScheduled = false
        
        let now = ProcessInfo().systemUptime
        let REMOVAL_DELAY = 0.2
        
        for touchView in self.overlayWindow.subviews
        {
            if touchView.isKind(of: FingerTipView.self) == false{
                continue
            }
            
            let thisView = touchView as! FingerTipView
            if thisView.shouldAutomaticallyRemoveAfterTimeout && now > (thisView.timestamp + REMOVAL_DELAY) {
                removeFingerTipWithHash(hash: thisView.tag, animated: true)
            }
        }
        
        if self.overlayWindow.subviews.count > 0 {
            scheduleFingerTipRemoval()
        }
    }
    
    func removeFingerTipWithHash(hash:NSInteger, animated:Bool)
    {
        let touchView = self.overlayWindow.viewWithTag(hash)
        if touchView == nil || touchView?.isKind(of: FingerTipView.self) == false {
            return
        }
        
        let thisView = touchView as! FingerTipView
        if thisView.fadingOut {
            return
        }
        
        let animationsWereEnabled = UIView.areAnimationsEnabled
        
        if animated {
            UIView.setAnimationsEnabled(true)
            UIView.beginAnimations(nil, context: nil)
            UIView.setAnimationDuration(self.fadeDuration)
        }
        
        thisView.frame = CGRect(x: thisView.center.x - thisView.frame.size.width,
                                y: thisView.center.y - thisView.frame.size.height,
                                width: thisView.frame.size.width  * 2,
                                height: thisView.frame.size.height * 2)
        
        thisView.alpha = 0.0
        
        if animated {
            UIView.commitAnimations()
            UIView.setAnimationsEnabled(animationsWereEnabled)
        }
        
        thisView.fadingOut = true
        thisView.perform(#selector(self.removeFromSuperview), with: nil, afterDelay: self.fadeDuration)
    }
    
    func shouldAutomaticallyRemoveFingerTipForTouch(touch:UITouch) -> Bool {
        var view = touch.view
        view = view?.hitTest(touch.location(in: view), with: nil)
        
        while (view != nil)
        {
            if (view?.isKind(of: UITableViewCell.self))!
            {
                for recognizer in touch.gestureRecognizers! {
                    if recognizer.isKind(of: UISwipeGestureRecognizer.self) {
                        return true
                    }
                }
            }
            
            if (view?.isKind(of: UITableView.self))! {
                if touch.gestureRecognizers?.count == 0{
                    return true
                }
            }
            
            view = view?.superview
        }
        
        return false
    }
    
    var overlayWindow:UIWindow {
        if _overlayWindow != nil {
            return _overlayWindow!
        }
        _overlayWindow = FingerTipOverlayWindow(frame: self.frame)
        _overlayWindow?.rootViewController = (_overlayWindow as! FingerTipOverlayWindow)._rootViewController
        _overlayWindow?.isUserInteractionEnabled = false
        _overlayWindow?.windowLevel = UIWindowLevelStatusBar
        _overlayWindow?.backgroundColor = UIColor.clear
        _overlayWindow?.isHidden = false
        
        return _overlayWindow!
    }
    
    var touchImage:UIImage {
        if _touchImage != nil {
            return _touchImage!
        }
        let clipPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 50, height: 50))
        UIGraphicsBeginImageContextWithOptions(clipPath.bounds.size, false0)
        
        let drawPath = UIBezierPath(arcCenter: CGPoint(x: 25, y: 25), radius: 22.0, startAngle: 0, endAngle: CGFloat(2*M_PI), clockwise: true)
 
        drawPath.lineWidth = 2.0;
        
        strokeColor.setStroke()
        fillColor.setFill()
        
        drawPath.stroke()
        drawPath.fill()
        
        clipPath.addClip()
        
        _touchImage = UIGraphicsGetImageFromCurrentImageContext()
        
        UIGraphicsEndImageContext()
        
        return _touchImage!
    }
}
cs



리뷰... 빨리 통과 되기를... 비나이다 비나이다