├── Preview
└── Preview1.png
├── Example
├── Source
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── PlayerPlay.imageset
│ │ │ ├── PlayerPlay.pdf
│ │ │ └── Contents.json
│ │ ├── PlayerStop.imageset
│ │ │ ├── PlayerStop.pdf
│ │ │ └── Contents.json
│ │ ├── RecordEnabled.imageset
│ │ │ ├── RecordEnabled.pdf
│ │ │ └── Contents.json
│ │ ├── RecordDisabled.imageset
│ │ │ ├── RecordDisabled.pdf
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── ControlsViewController.swift
│ ├── AppDelegate.swift
│ ├── OverlayWindow.swift
│ ├── Info.plist
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── MenuViewController.swift
│ ├── AlertViewController.swift
│ ├── OverlayViewController.swift
│ ├── WebViewController.swift
│ └── Overlay.storyboard
└── iOS Example.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── project.pbxproj
├── FakeTouch.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── Source
├── KIF
│ ├── IOHIDEvent+KIF.h
│ ├── LICENSE
│ ├── IOHIDEvent+KIF.m
│ └── README.md
├── Private Headers
│ ├── UIApplication+Private.h
│ ├── UIEvent+Private.h
│ └── UITouch+Private.h
├── FakeTouch.h
├── Additional
│ ├── TouchEvent.swift
│ ├── Touch.swift
│ ├── TouchPlayer.swift
│ └── TouchRecorder.swift
├── Info.plist
├── UIEvent+FakeTouch.swift
└── UITouch+FakeTouch.swift
├── FakeTouch.podspec
├── LICENSE
├── .gitignore
└── README.md
/Preview/Preview1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/watanabetoshinori/FakeTouch/HEAD/Preview/Preview1.png
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/PlayerPlay.imageset/PlayerPlay.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/watanabetoshinori/FakeTouch/HEAD/Example/Source/Assets.xcassets/PlayerPlay.imageset/PlayerPlay.pdf
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/PlayerStop.imageset/PlayerStop.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/watanabetoshinori/FakeTouch/HEAD/Example/Source/Assets.xcassets/PlayerStop.imageset/PlayerStop.pdf
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/RecordEnabled.imageset/RecordEnabled.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/watanabetoshinori/FakeTouch/HEAD/Example/Source/Assets.xcassets/RecordEnabled.imageset/RecordEnabled.pdf
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/RecordDisabled.imageset/RecordDisabled.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/watanabetoshinori/FakeTouch/HEAD/Example/Source/Assets.xcassets/RecordDisabled.imageset/RecordDisabled.pdf
--------------------------------------------------------------------------------
/FakeTouch.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Source/KIF/IOHIDEvent+KIF.h:
--------------------------------------------------------------------------------
1 | //
2 | // IOHIDEvent+KIF.h
3 | // KIF
4 | //
5 | // Created by Thomas Bonnin on 7/6/15.
6 | //
7 | //
8 |
9 | #import
10 |
11 | typedef struct __IOHIDEvent * IOHIDEventRef;
12 | IOHIDEventRef kif_IOHIDEventWithTouches(NSArray *touches) CF_RETURNS_RETAINED;
13 |
14 |
--------------------------------------------------------------------------------
/FakeTouch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Source/Private Headers/UIApplication+Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIApplication+Private.h
3 | // FakeTouch
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UIApplication (Private)
12 |
13 | - (UIEvent *)_touchesEvent;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Source/Private Headers/UIEvent+Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIEvent+Private.h
3 | // FakeTouch
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UIEvent (Private)
12 |
13 | - (void)_addTouch:(UITouch *)touch forDelayedDelivery:(BOOL)delayed;
14 |
15 | - (void)_clearTouches;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/PlayerPlay.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "PlayerPlay.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/PlayerStop.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "PlayerStop.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/RecordEnabled.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "RecordEnabled.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/RecordDisabled.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "RecordDisabled.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/FakeTouch.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'FakeTouch'
3 | s.version = '1.0.0'
4 | s.license = 'MIT'
5 | s.summary = 'FakeTouch is a framework that simulates iOS touch events.'
6 | s.homepage = 'https://github.com/watanabetoshinori/FakeTouch'
7 | s.author = "Watanabe Toshinori"
8 | s.source = { :git => 'https://github.com/watanabetoshinori/FakeTouch.git', :tag => s.version }
9 | s.frameworks = 'IOKit'
10 |
11 | s.ios.deployment_target = '12.0'
12 |
13 | s.source_files = 'Source/**/*.{h,m,swift}'
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/Example/Source/ControlsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ControlsViewController.swift
3 | // iOS Example
4 | //
5 | // Created by Watanabe Toshinori on 2/8/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ControlsViewController: UITableViewController {
12 |
13 | @IBOutlet weak var stepperLabel: UILabel!
14 |
15 | // MARK: - Viewcontroller lifecycle
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | }
21 |
22 | // MARK: - Actions
23 |
24 | @IBAction func stepperDidChanged(_ sender: UIStepper) {
25 | stepperLabel.text = String(Int(sender.value))
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Source/FakeTouch.h:
--------------------------------------------------------------------------------
1 | //
2 | // FakeTouch.h
3 | // FakeTouch
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for FakeTouch.
12 | FOUNDATION_EXPORT double FakeTouchVersionNumber;
13 |
14 | //! Project version string for FakeTouch.
15 | FOUNDATION_EXPORT const unsigned char FakeTouchVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 | #import "IOHIDEvent+KIF.h"
20 | #import "UIApplication+Private.h"
21 | #import "UIEvent+Private.h"
22 | #import "UITouch+Private.h"
23 |
--------------------------------------------------------------------------------
/Source/KIF/LICENSE:
--------------------------------------------------------------------------------
1 | KIF
2 | Copyright 2011-2016 Square, Inc.
3 | A full list of contributors is available at https://github.com/square/KIF/contributors
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
--------------------------------------------------------------------------------
/Example/Source/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // iOS Example
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | var overlayWindow: OverlayWindow?
17 |
18 | // MARK: - Application lifecycle
19 |
20 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
21 |
22 | self.overlayWindow = OverlayWindow(frame: UIScreen.main.bounds)
23 | overlayWindow?.makeKeyAndVisible()
24 |
25 | return true
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Source/Additional/TouchEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TouchEvent.swift
3 | // FakeTouch
4 | //
5 | // Created by Watanabe Toshinori on 2/7/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct TouchEvent: Codable {
12 |
13 | public let touches: [Touch]
14 |
15 | public let windowLevel: CGFloat
16 |
17 | public let timeInterval: TimeInterval
18 |
19 | }
20 |
21 | extension TouchEvent: CustomStringConvertible {
22 |
23 | public var description: String {
24 | let touchesDescription = touches.reduce(into: [String](), { $0.append($1.description) }).joined(separator: ",")
25 | return "{ timeInterval: \(timeInterval), windowLevel: \(windowLevel), touches: [\(touchesDescription)]}"
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Source/Private Headers/UITouch+Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // UITouch+Private.h
3 | // FakeTouch
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UITouch (Private)
12 |
13 | - (void)setPhase:(UITouchPhase)touchPhase;
14 |
15 | - (void)setTapCount:(NSUInteger)tapCount;
16 |
17 | - (void)setWindow:(UIWindow *)window;
18 |
19 | - (void)setView:(UIView *)view;
20 |
21 | - (void)_setLocationInWindow:(CGPoint)location resetPrevious:(BOOL)resetPrevious;
22 |
23 | - (void)_setIsFirstTouchForView:(BOOL)firstTouchForView;
24 |
25 | - (void)setIsTap:(BOOL)isTap;
26 |
27 | - (void)setTimestamp:(NSTimeInterval)timestamp;
28 |
29 | - (void)_setHidEvent:(IOHIDEventRef)event;
30 |
31 | - (void)setGestureView:(UIView *)view;
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/Source/UIEvent+FakeTouch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIEvent+FakeTouch.swift
3 | // FakeTouch
4 | //
5 | // Created by Watanabe Toshinori on 2/7/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIEvent {
12 |
13 | public class func send(touches: [UITouch]) {
14 | guard let event = UIApplication.shared._touchesEvent() else {
15 | return
16 | }
17 |
18 | event._clearTouches()
19 |
20 | touches.forEach { (touch) in
21 | event._add(touch, forDelayedDelivery: false)
22 | }
23 |
24 | UIApplication.shared.sendEvent(event)
25 |
26 | touches.forEach { (touch) in
27 | if touch.phase == .began || touch.phase == .moved {
28 | touch.setPhase(.stationary)
29 | touch.udpateTimestamp()
30 | }
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Watanabe Toshinori
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Example/Source/OverlayWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OverlayWindow.swift
3 | // iOS Example
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import FakeTouch
11 |
12 | class OverlayWindow: UIWindow {
13 |
14 | // MARK: - Initializing a UIWindow
15 |
16 | override init(frame: CGRect) {
17 | super.init(frame: frame)
18 |
19 | initialize()
20 | }
21 |
22 | required init?(coder aDecoder: NSCoder) {
23 | super.init(coder: aDecoder)
24 |
25 | initialize()
26 | }
27 |
28 | convenience init() {
29 | self.init(frame: .zero)
30 |
31 | initialize()
32 | }
33 |
34 | private func initialize() {
35 | TouchRecorder.shared.excludeWindow.append(self)
36 |
37 | windowLevel = UIWindow.Level.statusBar + 100
38 | rootViewController = UIStoryboard(name: "Overlay", bundle: nil).instantiateInitialViewController()!
39 | }
40 |
41 | // MARK: - Hit testing
42 |
43 | override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
44 | if let viewController = rootViewController as? OverlayViewController {
45 | return viewController.recorderButton.frame.contains(point)
46 | || viewController.playerButton.frame.contains(point)
47 | }
48 |
49 | return false
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Source/UITouch+FakeTouch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITouch+Simulate.swift
3 | // FakeTouch
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITouch {
12 |
13 | public var id: String {
14 | return String(format: "%p", unsafeBitCast(self, to: Int.self))
15 | }
16 |
17 | // MARK: - Initialize UITouch
18 |
19 | convenience public init(with location: CGPoint, in window: UIWindow) {
20 | self.init()
21 |
22 | let view = window.hitTest(location, with: nil)
23 |
24 | setWindow(window)
25 | setView(view)
26 | setTapCount(1)
27 | setPhase(.began)
28 | _setIsFirstTouch(forView: true)
29 | setIsTap(true)
30 |
31 | _setLocation(inWindow: location, resetPrevious: true)
32 | setTimestamp(ProcessInfo.processInfo.systemUptime)
33 |
34 | if responds(to: #selector(setGestureView(_:))) {
35 | setGestureView(view)
36 | }
37 |
38 | let event = kif_IOHIDEventWithTouches([self])
39 | _setHidEvent(event)
40 | }
41 |
42 | // MARK: - Updating values
43 |
44 | public func setLocation(_ location: CGPoint) {
45 | _setLocation(inWindow: location, resetPrevious: true)
46 | }
47 |
48 | public func udpateTimestamp() {
49 | setTimestamp(ProcessInfo.processInfo.systemUptime)
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Example/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | NSFaceIDUsageDescription
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Example/Source/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/Example/Source/MenuViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuViewController.swift
3 | // iOS Example
4 | //
5 | // Created by Watanabe Toshinori on 2/8/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import StoreKit
11 | import SafariServices
12 | import LocalAuthentication
13 |
14 | class MenuViewController: UITableViewController, SKStoreProductViewControllerDelegate {
15 |
16 | // MARK: - Viewcontroller lifecycle
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 |
21 | }
22 |
23 | // MARK: - Actions
24 |
25 | @IBAction func showStoreProductTapped(_ sender: Any) {
26 | let parameters = [SKStoreProductParameterITunesItemIdentifier: 364709193]
27 |
28 | let viewController = SKStoreProductViewController()
29 | viewController.delegate = self
30 | viewController.loadProduct(withParameters: parameters) { (result, error) in
31 | if let error = error {
32 | print(error)
33 | }
34 | }
35 | present(viewController, animated: true, completion: nil)
36 | }
37 |
38 | @IBAction func showSafariTapped(_ sender: Any) {
39 | let url = URL(string: "https://google.com")!
40 | let viewControlller = SFSafariViewController(url: url)
41 | present(viewControlller, animated: true, completion: nil)
42 | }
43 |
44 | @IBAction func showPermissionTapped(_ sender: Any) {
45 | let context = LAContext()
46 |
47 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Test") { (success, error) in
48 |
49 | }
50 | }
51 |
52 | // MARK: - SKStoreProductViewController Delegate
53 |
54 | func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
55 | navigationController?.dismiss(animated: true, completion: nil)
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/Example/Source/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 | #
51 | # Add this line if you want to avoid checking in source code from the Xcode workspace
52 | # *.xcworkspace
53 |
54 | # Carthage
55 | #
56 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
57 | # Carthage/Checkouts
58 |
59 | Carthage/Build
60 |
61 | # fastlane
62 | #
63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
64 | # screenshots whenever they are needed.
65 | # For more information about the recommended setup visit:
66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
67 |
68 | fastlane/report.xml
69 | fastlane/Preview.html
70 | fastlane/screenshots/**/*.png
71 | fastlane/test_output
72 |
73 | # Code Injection
74 | #
75 | # After new code Injection tools there's a generated folder /iOSInjectionProject
76 | # https://github.com/johnno1962/injectionforxcode
77 |
78 | iOSInjectionProject/
--------------------------------------------------------------------------------
/Example/Source/AlertViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlertViewController.swift
3 | // iOS Example
4 | //
5 | // Created by Watanabe Toshinori on 2/8/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class AlertViewController: UITableViewController {
12 |
13 | // MARK: - Viewcontroller lifecycle
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | }
19 |
20 | // MARK: - Actions
21 |
22 | @IBAction func alertTapped(_ sender: Any) {
23 | let alertController = UIAlertController(title: "Alert", message: "Alert!", preferredStyle: .alert)
24 |
25 | alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
26 | print("OK")
27 | }))
28 |
29 | present(alertController, animated: true, completion: nil)
30 | }
31 |
32 | @IBAction func confirmTapped(_ sender: Any) {
33 | let alertController = UIAlertController(title: "Confirm", message: "Are you sure?", preferredStyle: .alert)
34 |
35 | alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
36 | print("OK")
37 | }))
38 |
39 | alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) -> Void in
40 | print("Cancel")
41 | }))
42 |
43 | present(alertController, animated: true, completion: nil)
44 | }
45 |
46 | @IBAction func promptTapped(_ sender: Any) {
47 | let alertController = UIAlertController(title: "Prompt", message: "Please input text.", preferredStyle: .alert)
48 |
49 | alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
50 | let input = alertController.textFields?.first?.text ?? "None"
51 | print(input)
52 | }))
53 |
54 | alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) -> Void in
55 | print("Cancel")
56 | }))
57 |
58 | alertController.addTextField { (textField) -> Void in
59 | textField.placeholder = "Enter text"
60 | }
61 |
62 | present(alertController, animated: true, completion: nil)
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Source/Additional/Touch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Touch.swift
3 | // TouchTest2
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct Touch: Codable {
12 |
13 | public var id: String
14 |
15 | public var location: CGPoint
16 |
17 | public var phase: UITouch.Phase
18 |
19 | enum CodingKeys: String, CodingKey {
20 | case id
21 | case location
22 | case phase
23 | }
24 |
25 | public init(id: String, location: CGPoint, phase: UITouch.Phase) {
26 | self.id = id
27 | self.location = location
28 | self.phase = phase
29 | }
30 |
31 | public init(from decoder: Decoder) throws {
32 | let values = try decoder.container(keyedBy: CodingKeys.self)
33 | id = try values.decode(String.self, forKey: .id)
34 | location = try values.decode(CGPoint.self, forKey: .location)
35 |
36 | let phaseValue = try values.decode(String.self, forKey: .phase)
37 | phase = UITouch.Phase(value: phaseValue)
38 | }
39 |
40 | public func encode(to encoder: Encoder) throws {
41 | var container = encoder.container(keyedBy: CodingKeys.self)
42 | try container.encode(id, forKey: .id)
43 | try container.encode(location, forKey: .location)
44 | try container.encode(phase.value, forKey: .phase)
45 | }
46 |
47 | }
48 |
49 | extension Touch: CustomStringConvertible {
50 |
51 | public var description: String {
52 | return "{ id: \(id), location: \(location), phase: \(phase.value)}"
53 | }
54 | }
55 |
56 | extension UITouch.Phase {
57 |
58 | init(value: String) {
59 | switch value {
60 | case "began":
61 | self = .began
62 | case "moved":
63 | self = .moved
64 | case "ended":
65 | self = .ended
66 | case "cancelled":
67 | self = .cancelled
68 | default:
69 | self = .stationary
70 | }
71 | }
72 |
73 | var value: String {
74 | switch self {
75 | case .began:
76 | return "began"
77 | case .moved:
78 | return "moved"
79 | case .ended:
80 | return "ended"
81 | case .cancelled:
82 | return "cancelled"
83 | case .stationary:
84 | return "stationary"
85 | }
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/Example/Source/OverlayViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OverlayViewController.swift
3 | // iOS Example
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import FakeTouch
11 |
12 | class OverlayViewController: UIViewController {
13 |
14 | @IBOutlet weak var recorderButton: UIButton!
15 |
16 | @IBOutlet weak var playerButton: UIButton!
17 |
18 | var events = [TouchEvent]() {
19 | didSet {
20 | playerButton.isHidden = events.isEmpty
21 | }
22 | }
23 |
24 | var player: TouchPlayer?
25 |
26 | // MARK: - View lifecycle
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 |
31 | recorderButton.layer.cornerRadius = recorderButton.frame.height / 4
32 | recorderButton.layer.masksToBounds = true
33 |
34 | playerButton.layer.cornerRadius = playerButton.frame.height / 4
35 | playerButton.layer.masksToBounds = true
36 | playerButton.isHidden = true
37 | }
38 |
39 | // MARK: - Action
40 |
41 | @IBAction func recorderTapped(_ sender: Any) {
42 | let recorder = TouchRecorder.shared
43 |
44 | if recorder.isRecording {
45 | recorderButton.setImage(UIImage(named: "RecordDisabled")!, for: .normal)
46 |
47 | recorder.stop()
48 |
49 | } else {
50 | recorderButton.setImage(UIImage(named: "RecordEnabled")!, for: .normal)
51 |
52 | recorder.record { [weak self] (events, error) in
53 | if let error = error {
54 | print(error)
55 | return
56 | }
57 |
58 | self?.events = events
59 | }
60 | }
61 | }
62 |
63 | @IBAction func playerTapped(_ sender: Any) {
64 | if let player = player, player.isPlaying {
65 | playerButton.setImage(UIImage(named: "PlayerPlay"), for: .normal)
66 |
67 | player.stop()
68 |
69 | self.player = nil
70 |
71 | } else {
72 | playerButton.setImage(UIImage(named: "PlayerStop"), for: .normal)
73 |
74 | let player = TouchPlayer(events: events)
75 |
76 | player.play(finishPlayHandler: { [weak self] (error) in
77 | if let error = error {
78 | print(error.localizedDescription)
79 | return
80 | }
81 |
82 | self?.playerButton.setImage(UIImage(named: "PlayerPlay"), for: .normal)
83 | })
84 |
85 | self.player = player
86 | }
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/Source/Additional/TouchPlayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TouchPlayer.swift
3 | // TouchTest2
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public class TouchPlayer: NSObject {
12 |
13 | private enum State {
14 | case stopped
15 | case playing
16 | }
17 |
18 | public typealias PlayerFinishHandler = ((_ error: Error?) -> Void)
19 |
20 | public var isPlaying: Bool {
21 | return state == .playing
22 | }
23 |
24 | private var state: State = .stopped
25 |
26 | private var events: [TouchEvent]!
27 |
28 | private var touchObjects = [String: UITouch]()
29 |
30 | private var finishPlayHandler: PlayerFinishHandler?
31 |
32 | // MARK: - Initializing Player
33 |
34 | convenience public init(events: [TouchEvent]) {
35 | self.init()
36 | self.events = events
37 | }
38 |
39 | // MARK: - Controlling
40 |
41 | public func play(finishPlayHandler: PlayerFinishHandler?) {
42 | if isPlaying {
43 | return
44 | }
45 |
46 | self.finishPlayHandler = finishPlayHandler
47 |
48 | state = .playing
49 |
50 | let group = DispatchGroup()
51 |
52 | events.forEach { event in
53 | group.enter()
54 |
55 | DispatchQueue.main.asyncAfter(deadline: .now() + event.timeInterval) {
56 | defer {
57 | group.leave()
58 | }
59 |
60 | if self.state != .playing {
61 | return
62 | }
63 |
64 | let touchObjects = event.touches.map({ (touch) -> UITouch in
65 | return self.touchObject(for: touch, windowLevel: event.windowLevel)
66 | })
67 |
68 | UIEvent.send(touches: touchObjects)
69 | }
70 | }
71 |
72 | group.notify(queue: .main) {
73 | self.stop()
74 | }
75 | }
76 |
77 | public func stop() {
78 | if isPlaying == false {
79 | return
80 | }
81 |
82 | state = .stopped
83 |
84 | if let finishPlayHandler = finishPlayHandler {
85 | finishPlayHandler(nil)
86 | }
87 | }
88 |
89 | // MARK: - Getting UITouch
90 |
91 | private func touchObject(for touch: Touch, windowLevel: CGFloat) -> UITouch {
92 | if let touchObject = touchObjects[touch.id] {
93 | // Reusing previous UITouch
94 |
95 | touchObject.setLocation(touch.location)
96 | touchObject.setPhase(touch.phase)
97 | touchObject.udpateTimestamp()
98 |
99 | if touch.phase == .ended {
100 | touchObjects[touch.id] = nil
101 | }
102 |
103 | return touchObject
104 |
105 | } else {
106 | // Creating new UITouch
107 |
108 | let window = UIApplication.shared.windows.first(where: { $0.windowLevel.rawValue == windowLevel }) ?? UIApplication.shared.keyWindow!
109 |
110 | let touchObject = UITouch(with: touch.location, in: window)
111 | touchObjects[touch.id] = touchObject
112 | return touchObject
113 | }
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/Example/Source/WebViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebViewController.swift
3 | // iOS Example
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import WebKit
11 |
12 | class WebViewController: UIViewController {
13 |
14 | @IBOutlet weak var webView: WKWebView!
15 |
16 | // MARK: - Viewcontroller lifecycle
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 |
21 | let url = URL(string: "https://google.com")!
22 | let request = URLRequest(url: url)
23 | webView.load(request)
24 | webView.uiDelegate = self
25 | }
26 |
27 | }
28 |
29 | // webView.uiDelegate = self
30 |
31 | extension WebViewController: WKUIDelegate {
32 |
33 | // MARK: - WKUIDelegate
34 |
35 | func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
36 | if navigationAction.targetFrame?.isMainFrame != true {
37 | webView.load(navigationAction.request)
38 | }
39 |
40 | return nil
41 | }
42 |
43 | func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
44 | let alertController = UIAlertController(title: message, message: nil, preferredStyle: .alert)
45 |
46 | alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: { (action) -> Void in
47 | completionHandler()
48 | }))
49 |
50 | present(alertController, animated: true, completion: nil)
51 | }
52 |
53 | func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
54 | let alertController = UIAlertController(title: message, message: nil, preferredStyle: .alert)
55 |
56 | alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: { (action) -> Void in
57 | completionHandler(true)
58 | }))
59 |
60 | alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .default, handler: { (action) -> Void in
61 | completionHandler(false)
62 | }))
63 |
64 | present(alertController, animated: true, completion: nil)
65 | }
66 |
67 | func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
68 | let alertController = UIAlertController(title: prompt, message: nil, preferredStyle: .alert)
69 |
70 | alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: { (action) -> Void in
71 | let input = alertController.textFields?.first?.text
72 | completionHandler(input)
73 | }))
74 |
75 | alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .default, handler: { (action) -> Void in
76 | completionHandler(nil)
77 | }))
78 |
79 | alertController.addTextField { (textField) -> Void in
80 | textField.text = defaultText
81 | }
82 |
83 | present(alertController, animated: true, completion: nil)
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FakeTouch
2 |
3 | [](https://opensource.org/licenses/MIT)
4 |
5 | The FakeTouch framework allows you to recording / simulating the iOS touch events.
6 |
7 | This project was created using part of the [KIF](https://github.com/kif-framework/KIF) project and referring to [PTFakeTouch](https://github.com/Retr0-star/PTFakeTouch) and [ZSFakeTouch](https://github.com/Roshanzs/ZSFakeTouch) project.
8 |
9 |
10 | - [Features](#Features)
11 | - [Getting Started](#getting-started)
12 | - [Requirements](#requirements)
13 | - [Installation](#installation)
14 | - [Usage](#usage)
15 | - [Initialization](#initialization)
16 | - [Recording Touch Event](#recording-touch-event)
17 | - [Playing Touch Event](#playing-touch-event)
18 | - [Author](#author)
19 | - [License](#license)
20 | - [Acknowledgments](#acknowledgments)
21 |
22 | ## Features
23 | - [x] Simulates Touch Event.
24 | - [x] Recorder / Player class for Touch Event.
25 |
26 | ## Getting Started
27 |
28 | ### Requirements
29 |
30 | * iOS 12.0+
31 | * Xcode 10.0+
32 | * Swift 4.2+
33 |
34 | ### Installation
35 |
36 | **[Cocoa Pods](https://cocoapods.org)**
37 |
38 | ```sh
39 | pod "FakeTouch", :git => 'https://github.com/watanabetoshinori/FakeTouch.git', :branch => 'master'
40 | ```
41 |
42 | ## Usage
43 |
44 | ### Initialization
45 |
46 | Start by importing the package in the file you want to use it.
47 |
48 | ```swift
49 | import FakeTouch
50 | ```
51 |
52 | ### Recording Touch Event
53 |
54 | Record the touch event and make it available later.
55 |
56 | #### Initializing Recorder
57 |
58 | ```swift
59 | let recorder = TouchRecorder.shared
60 | ```
61 |
62 | #### Controlling Recording
63 |
64 | ```swift
65 | // Start recording
66 | recorder.record { [weak self] (events, error) in
67 | if let error = error {
68 | print(error)
69 | return
70 | }
71 |
72 | // Keep events for playing later.
73 | self?.events = events
74 | }
75 |
76 | // Stop recording
77 | recorder.stop()
78 | ```
79 |
80 | ### Playing Touch Event
81 |
82 | Play a touch event captured with the recorder.
83 |
84 | #### Initializing Player
85 |
86 | ````swift
87 | let player = TouchPlayer(events: events)
88 |
89 | // Keeping instance
90 | self.player = player
91 | ````
92 |
93 | #### Controlling Playback
94 |
95 | ```swift
96 | // Start simulation
97 | player.play(finishPlayHandler: { [weak self] (error) in
98 | if let error = error {
99 | print(error.localizedDescription)
100 | return
101 | }
102 | })
103 |
104 | // Stop simulation
105 | player.stop()
106 | ```
107 |
108 | ### Import and Export Touch Event
109 |
110 | Touch event supported JSON Codable protocol.
111 | You can export / import touch events.
112 |
113 | #### Export
114 |
115 | ```swift
116 | let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
117 | let jsonFileURL = documentDirectoryURL.appendingPathComponent("events.json")
118 |
119 | do {
120 | let data = try JSONEncoder().encode(events)
121 | try data.write(to: jsonFileURL)
122 |
123 | } catch {
124 | print(error)
125 | }
126 | ```
127 |
128 | #### Import
129 |
130 | ```swift
131 | let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
132 | let jsonFileURL = documentDirectoryURL.appendingPathComponent("events.json")
133 |
134 | do {
135 | let data = try Data(contentsOf: jsonFileURL)
136 | let events = try JSONDecoder().decode([TouchEvent].self, from: data)
137 | self.events = events
138 |
139 | } catch {
140 | print(error)
141 | }
142 | ```
143 |
144 | ## Example App
145 |
146 | Example App for FakeTouch. See the [Example](Example) directory.
147 |
148 | 
149 |
150 | ## Author
151 |
152 | Watanabe Toshinori – toshinori_watanabe@tiny.blue
153 |
154 | ## License
155 |
156 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
157 |
158 | This project uses some [KIF](https://github.com/kif-framework/KIF) source code. They are under the [KIF's License](Source/KIF/LICENSE).
159 |
160 | ## Acknowledgments
161 |
162 | This project refers to the code of the following projects:
163 |
164 | * [KIF](https://github.com/kif-framework/KIF)
165 | * [PTFakeTouch](https://github.com/Retr0-star/PTFakeTouch)
166 | * [ZSFakeTouch](https://github.com/Roshanzs/ZSFakeTouch)
167 |
--------------------------------------------------------------------------------
/Source/Additional/TouchRecorder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TouchRecorder.swift
3 | // TouchTest2
4 | //
5 | // Created by Watanabe Toshinori on 2/6/19.
6 | // Copyright © 2019 Watanabe Toshinori. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public class TouchRecorder: NSObject {
12 |
13 | private enum State {
14 | case stopped
15 | case recording
16 | case paused
17 | }
18 |
19 | public typealias RecorderFinishHandler = ((_ touchs: [TouchEvent], _ error: Error?) -> Void)
20 |
21 | public var isRecording: Bool {
22 | switch state {
23 | case .recording, .paused:
24 | return true
25 | case .stopped:
26 | return false
27 | }
28 | }
29 |
30 | public var excludeWindow = [UIWindow]()
31 |
32 | private var state: State = .stopped
33 |
34 | private var events = [TouchEvent]()
35 |
36 | private var startedDate = Date()
37 |
38 | private var finishRecordingHandler: RecorderFinishHandler?
39 |
40 | // MARK: - Initializing a Singleton
41 |
42 | static public let shared = TouchRecorder()
43 |
44 | override private init() {
45 |
46 | }
47 |
48 | // MARK: - Controlling Recorder
49 |
50 | public func record(finishRecordingHandler: RecorderFinishHandler?) {
51 | if isRecording {
52 | return
53 | }
54 |
55 | self.finishRecordingHandler = finishRecordingHandler
56 |
57 | events.removeAll()
58 |
59 | startedDate = Date()
60 |
61 | UIWindow.swizzlingSendEvent()
62 |
63 | state = .recording
64 | }
65 |
66 | public func stop() {
67 | if isRecording == false {
68 | return
69 | }
70 |
71 | UIWindow.swizzlingSendEvent()
72 |
73 | state = .stopped
74 |
75 | if let finishRecordingHandler = finishRecordingHandler {
76 | DispatchQueue.main.async {
77 | finishRecordingHandler(self.events, nil)
78 | }
79 | }
80 | }
81 |
82 | public func pause() {
83 | state = .paused
84 | }
85 |
86 | public func resume() {
87 | startedDate = Date()
88 |
89 | state = .recording
90 | }
91 |
92 | // MARK: - Adding Touch Event
93 |
94 | fileprivate func add(_ uiEvent: UIEvent, in window: UIWindow) {
95 | guard state == .recording else {
96 | return
97 | }
98 |
99 | if excludeWindow.firstIndex(of: window) != nil {
100 | return
101 | }
102 |
103 | guard let uiTouches = uiEvent.allTouches, uiTouches.isEmpty == false else {
104 | return
105 | }
106 |
107 | let windowLevel = window.windowLevel.rawValue
108 | let timeInterval = Date().timeIntervalSince(startedDate)
109 |
110 | let touches = uiTouches.map { (uiTouch) -> Touch in
111 | return Touch(id: uiTouch.id,
112 | location: uiTouch.location(in: window),
113 | phase: uiTouch.phase)
114 | }
115 |
116 | let event = TouchEvent(
117 | touches: touches,
118 | windowLevel: windowLevel,
119 | timeInterval: timeInterval
120 | )
121 |
122 | DispatchQueue.main.async {
123 | self.events.append(event)
124 | }
125 | }
126 |
127 | }
128 |
129 | // MARK: - UIWindow extesnion for recording Touch event
130 |
131 | extension UIWindow {
132 |
133 | @objc func swizzledSendEvent(_ event: UIEvent) {
134 | TouchRecorder.shared.add(event, in: self)
135 |
136 | swizzledSendEvent(event)
137 | }
138 |
139 | fileprivate class func swizzlingSendEvent() {
140 | let origin = #selector(UIWindow.sendEvent(_:))
141 | let swizzled = #selector(UIWindow.swizzledSendEvent(_:))
142 |
143 | let `class` = object_getClass(UIWindow(frame: .zero))
144 |
145 | guard let originalMethod = class_getInstanceMethod(`class`, origin),
146 | let swizzledMethod = class_getInstanceMethod(`class`, swizzled) else {
147 | fatalError("Failed to get instance method.")
148 | }
149 |
150 | method_exchangeImplementations(originalMethod, swizzledMethod)
151 | }
152 |
153 | }
154 |
--------------------------------------------------------------------------------
/Example/Source/Overlay.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
34 |
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 |
--------------------------------------------------------------------------------
/Source/KIF/IOHIDEvent+KIF.m:
--------------------------------------------------------------------------------
1 | //
2 | // IOHIDEvent+KIF.m
3 | // KIF
4 | //
5 | // Created by Thomas Bonnin on 7/6/15.
6 | //
7 | //
8 |
9 | #import
10 | #import "IOHIDEvent+KIF.h"
11 | #import
12 |
13 | #define IOHIDEventFieldBase(type) (type << 16)
14 | #ifdef __LP64__
15 | typedef double IOHIDFloat;
16 | #else
17 | typedef float IOHIDFloat;
18 | #endif
19 | typedef UInt32 IOOptionBits;
20 | typedef uint32_t IOHIDDigitizerTransducerType;
21 | typedef uint32_t IOHIDEventField;
22 | typedef uint32_t IOHIDEventType;
23 |
24 | void IOHIDEventAppendEvent(IOHIDEventRef event, IOHIDEventRef childEvent);
25 | void IOHIDEventSetIntegerValue(IOHIDEventRef event, IOHIDEventField field, int value);
26 | void IOHIDEventSetSenderID(IOHIDEventRef event, uint64_t sender);
27 |
28 | enum {
29 | kIOHIDDigitizerTransducerTypeStylus = 0,
30 | kIOHIDDigitizerTransducerTypePuck,
31 | kIOHIDDigitizerTransducerTypeFinger,
32 | kIOHIDDigitizerTransducerTypeHand
33 | };
34 |
35 | enum {
36 | kIOHIDEventTypeNULL, // 0
37 | kIOHIDEventTypeVendorDefined,
38 | kIOHIDEventTypeButton,
39 | kIOHIDEventTypeKeyboard,
40 | kIOHIDEventTypeTranslation,
41 | kIOHIDEventTypeRotation, // 5
42 | kIOHIDEventTypeScroll,
43 | kIOHIDEventTypeScale,
44 | kIOHIDEventTypeZoom,
45 | kIOHIDEventTypeVelocity,
46 | kIOHIDEventTypeOrientation, // 10
47 | kIOHIDEventTypeDigitizer,
48 | kIOHIDEventTypeAmbientLightSensor,
49 | kIOHIDEventTypeAccelerometer,
50 | kIOHIDEventTypeProximity,
51 | kIOHIDEventTypeTemperature, // 15
52 | kIOHIDEventTypeNavigationSwipe,
53 | kIOHIDEventTypePointer,
54 | kIOHIDEventTypeProgress,
55 | kIOHIDEventTypeMultiAxisPointer,
56 | kIOHIDEventTypeGyro, // 20
57 | kIOHIDEventTypeCompass,
58 | kIOHIDEventTypeZoomToggle,
59 | kIOHIDEventTypeDockSwipe, // just like kIOHIDEventTypeNavigationSwipe, but intended for consumption by Dock
60 | kIOHIDEventTypeSymbolicHotKey,
61 | kIOHIDEventTypePower, // 25
62 | kIOHIDEventTypeLED,
63 | kIOHIDEventTypeFluidTouchGesture, // This will eventually superseed Navagation and Dock swipes
64 | kIOHIDEventTypeBoundaryScroll,
65 | kIOHIDEventTypeBiometric,
66 | kIOHIDEventTypeUnicode, // 30
67 | kIOHIDEventTypeAtmosphericPressure,
68 | kIOHIDEventTypeUndefined,
69 | kIOHIDEventTypeCount, // This should always be last
70 |
71 |
72 | // DEPRECATED:
73 | kIOHIDEventTypeSwipe = kIOHIDEventTypeNavigationSwipe,
74 | kIOHIDEventTypeMouse = kIOHIDEventTypePointer
75 | };
76 |
77 | enum {
78 | kIOHIDDigitizerEventRange = 0x00000001,
79 | kIOHIDDigitizerEventTouch = 0x00000002,
80 | kIOHIDDigitizerEventPosition = 0x00000004,
81 | kIOHIDDigitizerEventStop = 0x00000008,
82 | kIOHIDDigitizerEventPeak = 0x00000010,
83 | kIOHIDDigitizerEventIdentity = 0x00000020,
84 | kIOHIDDigitizerEventAttribute = 0x00000040,
85 | kIOHIDDigitizerEventCancel = 0x00000080,
86 | kIOHIDDigitizerEventStart = 0x00000100,
87 | kIOHIDDigitizerEventResting = 0x00000200,
88 | kIOHIDDigitizerEventSwipeUp = 0x01000000,
89 | kIOHIDDigitizerEventSwipeDown = 0x02000000,
90 | kIOHIDDigitizerEventSwipeLeft = 0x04000000,
91 | kIOHIDDigitizerEventSwipeRight = 0x08000000,
92 | kIOHIDDigitizerEventSwipeMask = 0xFF000000,
93 | };
94 | enum {
95 | kIOHIDEventFieldDigitizerX = IOHIDEventFieldBase(kIOHIDEventTypeDigitizer),
96 | kIOHIDEventFieldDigitizerY,
97 | kIOHIDEventFieldDigitizerZ,
98 | kIOHIDEventFieldDigitizerButtonMask,
99 | kIOHIDEventFieldDigitizerType,
100 | kIOHIDEventFieldDigitizerIndex,
101 | kIOHIDEventFieldDigitizerIdentity,
102 | kIOHIDEventFieldDigitizerEventMask,
103 | kIOHIDEventFieldDigitizerRange,
104 | kIOHIDEventFieldDigitizerTouch,
105 | kIOHIDEventFieldDigitizerPressure,
106 | kIOHIDEventFieldDigitizerAuxiliaryPressure, //BarrelPressure
107 | kIOHIDEventFieldDigitizerTwist,
108 | kIOHIDEventFieldDigitizerTiltX,
109 | kIOHIDEventFieldDigitizerTiltY,
110 | kIOHIDEventFieldDigitizerAltitude,
111 | kIOHIDEventFieldDigitizerAzimuth,
112 | kIOHIDEventFieldDigitizerQuality,
113 | kIOHIDEventFieldDigitizerDensity,
114 | kIOHIDEventFieldDigitizerIrregularity,
115 | kIOHIDEventFieldDigitizerMajorRadius,
116 | kIOHIDEventFieldDigitizerMinorRadius,
117 | kIOHIDEventFieldDigitizerCollection,
118 | kIOHIDEventFieldDigitizerCollectionChord,
119 | kIOHIDEventFieldDigitizerChildEventMask,
120 | kIOHIDEventFieldDigitizerIsDisplayIntegrated,
121 | kIOHIDEventFieldDigitizerQualityRadiiAccuracy,
122 | };
123 | IOHIDEventRef IOHIDEventCreateDigitizerEvent(CFAllocatorRef allocator, AbsoluteTime timeStamp, IOHIDDigitizerTransducerType type,
124 | uint32_t index, uint32_t identity, uint32_t eventMask, uint32_t buttonMask,
125 | IOHIDFloat x, IOHIDFloat y, IOHIDFloat z, IOHIDFloat tipPressure, IOHIDFloat barrelPressure,
126 | Boolean range, Boolean touch, IOOptionBits options);
127 | IOHIDEventRef IOHIDEventCreateDigitizerFingerEventWithQuality(CFAllocatorRef allocator, AbsoluteTime timeStamp,
128 | uint32_t index, uint32_t identity, uint32_t eventMask,
129 | IOHIDFloat x, IOHIDFloat y, IOHIDFloat z, IOHIDFloat tipPressure, IOHIDFloat twist,
130 | IOHIDFloat minorRadius, IOHIDFloat majorRadius, IOHIDFloat quality, IOHIDFloat density, IOHIDFloat irregularity,
131 | Boolean range, Boolean touch, IOOptionBits options);
132 |
133 | IOHIDEventRef kif_IOHIDEventWithTouches(NSArray *touches) {
134 | uint64_t abTime = mach_absolute_time();
135 | AbsoluteTime timeStamp;
136 | timeStamp.hi = (UInt32)(abTime >> 32);
137 | timeStamp.lo = (UInt32)(abTime);
138 |
139 | IOHIDEventRef handEvent = IOHIDEventCreateDigitizerEvent(kCFAllocatorDefault, // allocator
140 | timeStamp, // timestamp
141 | kIOHIDDigitizerTransducerTypeHand, // type
142 | 0, // index
143 | 0, // identity
144 | kIOHIDDigitizerEventTouch, // eventMask
145 | 0, // buttonMask
146 | 0, // x
147 | 0, // y
148 | 0, // z
149 | 0, // tipPressure
150 | 0, // barrelPressure
151 | 0, // range
152 | true, // touch
153 | 0); // options
154 | IOHIDEventSetIntegerValue(handEvent, kIOHIDEventFieldDigitizerIsDisplayIntegrated, true);
155 |
156 | for (UITouch *touch in touches)
157 | {
158 | uint32_t eventMask = (touch.phase == UITouchPhaseMoved) ? kIOHIDDigitizerEventPosition : (kIOHIDDigitizerEventRange | kIOHIDDigitizerEventTouch);
159 | uint32_t isTouching = (touch.phase == UITouchPhaseEnded) ? 0 : 1;
160 |
161 | CGPoint touchLocation = [touch locationInView:touch.window];
162 |
163 | IOHIDEventRef fingerEvent = IOHIDEventCreateDigitizerFingerEventWithQuality(kCFAllocatorDefault, // allocator
164 | timeStamp, // timestamp
165 | (UInt32)[touches indexOfObject:touch] + 1, //index
166 | 2, // identity
167 | eventMask, // eventMask
168 | (IOHIDFloat)touchLocation.x, // x
169 | (IOHIDFloat)touchLocation.y, // y
170 | 0.0, // z
171 | 0, // tipPressure
172 | 0, // twist
173 | 5.0, // minor radius
174 | 5.0, // major radius
175 | 1.0, // quality
176 | 1.0, // density
177 | 1.0, // irregularity
178 | (IOHIDFloat)isTouching, // range
179 | (IOHIDFloat)isTouching, // touch
180 | 0); // options
181 | IOHIDEventSetIntegerValue(fingerEvent, kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1);
182 |
183 | IOHIDEventAppendEvent(handEvent, fingerEvent);
184 | CFRelease(fingerEvent);
185 | }
186 |
187 | return handEvent;
188 | }
189 |
--------------------------------------------------------------------------------
/Source/KIF/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/kif-framework/KIF) [](https://github.com/Carthage/Carthage) [](https://cocoapods.org)
2 |
3 | **IMPORTANT! Even though KIF is used to test your UI, you need to add it to your Unit Test target, _not_ your UI Test target. The magic of KIF is that it allows you to drive your UI from your unit tests and reap all the advantages of testing in-process.**
4 |
5 | KIF iOS Integration Testing Framework
6 | =====================================
7 |
8 | KIF, which stands for Keep It Functional, is an iOS integration test framework. It allows for easy automation of iOS apps by leveraging the accessibility attributes that the OS makes available for those with visual disabilities.
9 |
10 | KIF builds and performs the tests using a standard `XCTest` testing target. Testing is conducted synchronously in the main thread (running the run loop to force the passage of time) allowing for more complex logic and composition. This also allows KIF to take advantage of the Xcode Test Navigator, command line build tools, and Bot test reports.
11 |
12 | **KIF uses undocumented Apple APIs.** This is true of most iOS testing frameworks, and is safe for testing purposes, but it's important that KIF does not make it into production code, as it will get your app submission denied by Apple. Follow the instructions below to ensure that KIF is configured correctly for your project.
13 |
14 | Features
15 | --------
16 |
17 | #### Minimizes Indirection
18 | All of the tests for KIF are written in Objective-C. This allows for maximum integration with your code while minimizing the number of layers you have to build.
19 |
20 | #### Easy Configuration
21 | KIF integrates directly into your Xcode project, so there's no need to run an additional web server or install any additional packages.
22 |
23 | #### Wide OS and Xcode coverage
24 | KIF's test suite is being run against iOS 8+ and Xcode 7+. Lower versions will likely still work, but your mileage may vary. We do our best to retain backwards compatibility as much as possible.
25 |
26 | #### Test Like a User
27 | KIF attempts to imitate actual user input. Automation is done using tap events wherever possible.
28 |
29 | #### Automatic Integration with Xcode Testing Tools
30 | You can easily run a single KIF test with the Test Navigator or kick off nightly acceptance tests with Bots.
31 |
32 | See KIF in Action
33 | -----------------
34 |
35 | KIF uses techniques described below to validate its internal functionality. You can see a test suite that exercises its entire functionality by simply building and testing the KIF scheme with ⌘U. Look at the tests in the "KIF Tests" group for ideas on how to build your own tests.
36 |
37 | Installation (with CocoaPods)
38 | -----------------------------
39 |
40 | [CocoaPods](http://cocoapods.org) are the easiest way to get set up with KIF.
41 |
42 | The first thing you will want to do is set up a test target you will be using for KIF. You may already have one named *MyApplication*_Tests if you selected to automatically create unit tests. If you did, you can keep using it if you aren't using it for unit tests. Otherwise, follow these directions to create a new one.
43 |
44 | Select your project in Xcode and click on "Add Target" in the bottom left corner of the editor. Select iOS -> Test -> iOS Unit Testing Bundle. Give it a product name like "Acceptance Tests", "UI Tests", or something that indicates the intent of your testing process.
45 |
46 | The testing target will add a header and implementation file, likely "Acceptance_Tests.m/h" to match your target name. Delete those.
47 |
48 | Once your test target set up, add the following to your Podfile file. Use your target's name as appropriate.
49 |
50 | ```Ruby
51 | target 'Your Apps' do
52 | ...
53 | end
54 |
55 | target 'Acceptance Tests' do
56 | pod 'KIF', :configurations => ['Debug']
57 | end
58 | ```
59 |
60 | After running `pod install` complete the tasks in [**Final Test Target Configurations**](#final-test-target-configurations) below for the final details on getting your tests to run.
61 |
62 | Installation (from GitHub)
63 | --------------------------
64 |
65 | To install KIF, you'll need to link the libKIF static library directly into your application. Download the source from the [kif-framework/KIF](https://github.com/kif-framework/KIF/) and follow the instructions below. The screenshots are from Xcode 6 on Yosemite, but the instructions should be the same for Xcode 5 or later on any OS version.
66 |
67 | We'll be using a simple project as an example, and you can find it in `Documentation/Examples/Testable Swift` in this repository.
68 |
69 | 
70 |
71 |
72 | ### Add KIF to your project files
73 | The first step is to add the KIF project into the ./Frameworks/KIF subdirectory of your existing app. If your project uses Git for version control, you can use submodules to make updating in the future easier:
74 |
75 | ```
76 | cd /path/to/MyApplicationSource
77 | mkdir Frameworks
78 | git submodule add https://github.com/kif-framework/KIF.git Frameworks/KIF
79 | ```
80 |
81 | If you're not using Git, simply download the source and copy it into the `./Frameworks/KIF` directory.
82 |
83 | ### Add KIF to Your Workspace
84 | Let your project know about KIF by adding the KIF project into a workspace along with your main project. Find the `KIF.xcodeproj` file in Finder and drag it into the Project Navigator (⌘1).
85 |
86 | 
87 |
88 |
89 | ### Create a Testing Target
90 | You'll need to create a test target for your app. You may already have one named *MyApplication*Tests if you selected to automatically create unit tests when you created the project. If you did, you can keep using it if you aren't using it for unit tests. Otherwise, follow these directions to create a new one.
91 |
92 | Select your project in Xcode and click on "Add Target" in the bottom left corner of the editor. Select iOS -> Test -> iOS Unit Testing Bundle. Give it a product name like "Acceptance Tests", "UI Tests", or something that indicates the intent of your testing process.
93 |
94 | The testing target will add a header and implementation file, likely "Acceptance_Tests.m/h" to match your target name. Delete those.
95 |
96 | ### Configure the Testing Target
97 | Now that you have a target for your tests, add the tests to that target. With the project settings still selected in the Project Navigator, and the new integration tests target selected in the project settings, select the "Build Phases" tab. Under the "Link Binary With Libraries" section, hit the "+" button. In the sheet that appears, select "libKIF.a" and click "Add". Repeat the process for CoreGraphics.framework and QuartzCore.framework.
98 |
99 | KIF requires the IOKit.framework, but it is not located with the other system frameworks. To link to it, add "-framework IOKit" to the "Other Linker Flags" build setting.
100 |
101 | 
102 |
103 | 
104 |
105 | KIF takes advantage of Objective C's ability to add categories on an object, but this isn't enabled for static libraries by default. To enable this, add the `-ObjC` flag to the "Other Linker Flags" build setting on your test bundle target as shown below.
106 |
107 | 
108 |
109 | Read **Final Test Target Configurations** below for the final details on getting your tests to run.
110 |
111 | Installing Accessibility Identifier Tests
112 | -----------------------------------------
113 |
114 | Normally you identify a UI element via its accessibility label so that KIF simulates the interactions of a real user as closely as possible. In some cases, however, you may have to use accessibility identifiers, which are not exposed to users. If using CocoaPods, install the additional identifier-based KIF tests via the Identifier CocoaPods subspec:
115 |
116 | ```
117 | pod 'KIF/IdentifierTests'
118 | ```
119 |
120 | If not using CocoaPods, the identifier-based KIF tests can be added by including "KIFUITestActor-IdentifierTests.h".
121 |
122 | Final Test Target Configurations
123 | --------------------------------
124 |
125 | You need your tests to run hosted in your application. **Xcode does this for you by default** when creating a new testing bundle target, but if you're migrating an older bundle, follow the steps below.
126 |
127 | First add your application by selecting "Build Phases", expanding the "Target Dependencies" section, clicking on the "+" button, and in the new sheet that appears selecting your application target and clicking "Add".
128 |
129 | Next, configure your bundle loader. In "Build Settings", expand "Linking" and edit "Bundle Loader" to be "$(TEST_HOST)". Expand the "Testing" section and edit "Test Host" to be "$(BUILT_PRODUCTS_DIR)/MyApplication.app/MyApplication" where "MyApplication" is the name of your app. Also make sure that "Wrapper Extension" is set to "xctest".
130 |
131 | The last step is to configure your unit tests to run when you trigger a test (⌘U). Click on your scheme name and select "Edit Scheme…". Click on "Test" in the sidebar followed by the "+" in the bottom left corner. Select your testing target and click "OK".
132 |
133 | ## Example test cases
134 | With your project configured to use KIF, it's time to start writing tests. There are two main classes used in KIF testing: the test case (`KIFTestCase`, subclass of `XCTestCase`) and the UI test actor (`KIFUITestActor`). The XCTest test runner loads the test case classes and executes their test. Inside these tests, the tester performs the UI operations which generally imitate a user interaction. Three of the most common tester actions are "tap this view," "enter text into this view," and "wait for this view." These steps are included as factory methods on `KIFUITestActor` in the base KIF implementation.
135 |
136 | KIF relies on the built-in accessibility of iOS to perform its test steps. As such, it's important that your app is fully accessible. This is also a great way to ensure that your app is usable by everyone. Giving your views reasonable labels is usually a good place to start when making your application accessible. More details are available in [Apple's Documentation](http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html#//apple_ref/doc/uid/TP40008785-CH102-SW5).
137 |
138 | The first step is to create a test class to test some functionality. In our case, we will create a login test (`LoginTests`). Create a new class that inherits from KIFTestCase. You may have to update the import to point to ``. The test method name provides a unique identifier. Your `KIFTestCase` subclass should look something like this:
139 |
140 | *LoginTestCase.h*
141 |
142 | ```objective-c
143 | #import
144 |
145 | @interface LoginTests : KIFTestCase
146 | @end
147 | ```
148 |
149 | *LoginTestCase.m*
150 |
151 | ```objective-c
152 | #import "LoginTests.h"
153 | #import "KIFUITestActor+EXAdditions.h"
154 |
155 | @implementation LoginTests
156 |
157 | - (void)beforeEach
158 | {
159 | [tester navigateToLoginPage];
160 | }
161 |
162 | - (void)afterEach
163 | {
164 | [tester returnToLoggedOutHomeScreen];
165 | }
166 |
167 | - (void)testSuccessfulLogin
168 | {
169 | [tester enterText:@"user@example.com" intoViewWithAccessibilityLabel:@"Login User Name"];
170 | [tester enterText:@"thisismypassword" intoViewWithAccessibilityLabel:@"Login Password"];
171 | [tester tapViewWithAccessibilityLabel:@"Log In"];
172 |
173 | // Verify that the login succeeded
174 | [tester waitForTappableViewWithAccessibilityLabel:@"Welcome"];
175 | }
176 |
177 | @end
178 | ```
179 |
180 | Most of the tester actions in the test are already defined by the KIF framework, but `-navigateToLoginPage` and `-returnToLoggedOutHomeScreen` are not. These are examples of custom actions which are specific to your application. Adding such steps is easy, and is done using a factory method in a category of `KIFUITestActor`, similar to how we added the scenario.
181 |
182 | *KIFUITestActor+EXAdditions.h*
183 |
184 | ```objective-c
185 | #import
186 |
187 | @interface KIFUITestActor (EXAdditions)
188 |
189 | - (void)navigateToLoginPage;
190 | - (void)returnToLoggedOutHomeScreen;
191 |
192 | @end
193 | ```
194 |
195 | *KIFUITestActor+EXAdditions.m*
196 |
197 | ```objective-c
198 | #import "KIFUITestActor+EXAdditions.h"
199 |
200 | @implementation KIFUITestActor (EXAdditions)
201 |
202 | - (void)navigateToLoginPage
203 | {
204 | [self tapViewWithAccessibilityLabel:@"Login/Sign Up"];
205 | [self tapViewWithAccessibilityLabel:@"Skip this ad"];
206 | }
207 |
208 | - (void)returnToLoggedOutHomeScreen
209 | {
210 | [self tapViewWithAccessibilityLabel:@"Logout"];
211 | [self tapViewWithAccessibilityLabel:@"Logout"]; // Dismiss alert.
212 | }
213 |
214 | @end
215 | ```
216 |
217 | Everything should now be configured. When you run the integration tests using the test button, ⌘U, or the Xcode Test Navigator (⌘5).
218 |
219 | Use with other testing frameworks
220 | ---------------------------------
221 |
222 | `KIFTestCase` is not necessary for running KIF tests. Tests can run directly in `XCTestCase` or any subclass. The basic requirement is that when you call `tester` or `system`, `self` must be an instance of `XCTestCase` and you must call `KIFEnableAccessibility` in `setUp`.
223 |
224 | For example, the following [Specta](https://github.com/specta/specta) test works without any changes to KIF or Specta:
225 |
226 | ```objective-c
227 | #import
228 | #import
229 |
230 | SpecBegin(App)
231 |
232 | describe(@"Tab controller", ^{
233 |
234 | it(@"should show second view when I tap on the second tab", ^{
235 | [tester tapViewWithAccessibilityLabel:@"Second" traits:UIAccessibilityTraitButton];
236 | [tester waitForViewWithAccessibilityLabel:@"Second View"];
237 | });
238 |
239 | });
240 |
241 | SpecEnd
242 | ```
243 |
244 | If you want to use KIF with a test runner that does not subclass `XCTestCase`, your runner class just needs to implement the `KIFTestActorDelegate` protocol which contains two required methods.
245 |
246 | - (void)failWithException:(NSException *)exception stopTest:(BOOL)stop;
247 | - (void)failWithExceptions:(NSArray *)exceptions stopTest:(BOOL)stop;
248 |
249 | In the first case, the test runner should log the exception and halt the test execution if `stop` is `YES`. In the second, the runner should log all the exceptions and halt the test execution if `stop` is `YES`. The exceptions take advantage of KIF's extensions to `NSException` that include the `lineNumber` and `filename` in the exception's `userData` to record the error's origin.
250 |
251 | ## Use with Swift
252 |
253 | Since it's easy to combine Swift and Objective-C code in a single project, KIF is fully capable of testing apps written in both Objective-C and Swift.
254 |
255 | If you want to write your test cases in Swift, you'll need to keep two things in mind.
256 |
257 | 1. Your test bundle's bridging header will need to `#import `, since KIF is a static library and not a header.
258 | 2. The `tester` and `system` keywords are C preprocessor macros which aren't available in Swift. You can easily write a small extension to `XCTestCase` or any other class to access them:
259 |
260 | ```swift
261 | extension XCTestCase {
262 | func tester(file : String = #file, _ line : Int = #line) -> KIFUITestActor {
263 | return KIFUITestActor(inFile: file, atLine: line, delegate: self)
264 | }
265 |
266 | func system(file : String = #file, _ line : Int = #line) -> KIFSystemTestActor {
267 | return KIFSystemTestActor(inFile: file, atLine: line, delegate: self)
268 | }
269 | }
270 |
271 | extension KIFTestActor {
272 | func tester(file : String = #file, _ line : Int = #line) -> KIFUITestActor {
273 | return KIFUITestActor(inFile: file, atLine: line, delegate: self)
274 | }
275 |
276 | func system(file : String = #file, _ line : Int = #line) -> KIFSystemTestActor {
277 | return KIFSystemTestActor(inFile: file, atLine: line, delegate: self)
278 | }
279 | }
280 | ```
281 |
282 | See Documentation/Examples/Testable Swift for sample code.
283 |
284 |
285 | Troubleshooting
286 | ---------------
287 |
288 | ### Simulator launches but app doesn't appear, steps time out after 10 seconds
289 |
290 | This issue occurs when XCTest does not have a valid test host. Reread the instructions above with regards to the "Bundle Loader" and "Test Host" settings. You may have missed something.
291 |
292 | ### Step fails because a view cannot be found
293 |
294 | If KIF is failing to find a view, the most likely cause is that the view doesn't have its accessibility label set. If the view is defined in a xib, then the label can be set using the inspector. If it's created programmatically, simply set the accessibilityLabel attribute to the desired label.
295 |
296 | If the label is definitely set correctly, take a closer look at the error given by KIF. This error should tell you more specifically why the view was not accessible. If you are using `-waitForTappableViewWithAccessibilityLabel:`, then make sure the view is actually tappable. For items such as labels which cannot become the first responder, you may need to use `-waitForViewWithAccessibilityLabel:` instead.
297 |
298 | ### Unrecognized selector when first trying to run
299 |
300 | If the first time you try to run KIF you get the following error:
301 |
302 | 2011-06-13 13:54:53.295 Testable (Integration Tests)[12385:207] -[NSFileManager createUserDirectory:]: unrecognized selector sent to instance 0x4e02830
303 | 2011-06-13 13:54:53.298 Testable (Integration Tests)[12385:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSFileManager createUserDirectory:]: unrecognized selector sent to instance 0x4e02830'
304 |
305 | or if you get another "unrecognized selector" error inside the KIF code, make sure that you've properly set the -ObjC flag as described above. Without this flag your app can't access the category methods that are necessary for KIF to work properly.
306 |
307 | Continuous Integration
308 | ----------------------
309 |
310 | A continuous integration (CI) process is highly recommended and is extremely useful in ensuring that your application stays functional. The easiest way to do this will be with Xcode, either using Bots, or Jenkins or another tool that uses xcodebuild. For tools using xcodebuild, review the manpage for instructions on using test destinations.
311 |
312 | Contributing
313 | ------------
314 |
315 | We're glad you're interested in KIF, and we'd love to see where you take it.
316 |
317 | Any contributors to the master KIF repository must sign the [Individual Contributor License Agreement (CLA)](https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1). It's a short form that covers our bases and makes sure you're eligible to contribute.
318 |
319 | When you have a change you'd like to see in the master repository, [send a pull request](https://github.com/kif-framework/KIF/pulls). Before we merge your request, we'll make sure you're in the list of people who have signed a CLA.
320 |
321 | Thanks, and happy testing!
322 |
--------------------------------------------------------------------------------
/Example/iOS Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1386A531220C820200E72CE6 /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1386A530220C820200E72CE6 /* AlertViewController.swift */; };
11 | 1386A533220C861C00E72CE6 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1386A532220C861C00E72CE6 /* MenuViewController.swift */; };
12 | 1386A55B220D7C1E00E72CE6 /* ControlsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1386A55A220D7C1E00E72CE6 /* ControlsViewController.swift */; };
13 | 13E77CCA220AC38700DC0D7D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E77CC9220AC38700DC0D7D /* AppDelegate.swift */; };
14 | 13E77CCF220AC38700DC0D7D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13E77CCD220AC38700DC0D7D /* Main.storyboard */; };
15 | 13E77CD1220AC38B00DC0D7D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13E77CD0220AC38B00DC0D7D /* Assets.xcassets */; };
16 | 13E77CD4220AC38B00DC0D7D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13E77CD2220AC38B00DC0D7D /* LaunchScreen.storyboard */; };
17 | 13E77D16220ACC2F00DC0D7D /* OverlayWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E77D15220ACC2F00DC0D7D /* OverlayWindow.swift */; };
18 | 13E77D18220ACC5A00DC0D7D /* OverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E77D17220ACC5A00DC0D7D /* OverlayViewController.swift */; };
19 | 13E77D1A220ACD4F00DC0D7D /* Overlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13E77D19220ACD4F00DC0D7D /* Overlay.storyboard */; };
20 | 13E77D1C220ACE4700DC0D7D /* FakeTouch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13E77CE2220AC59D00DC0D7D /* FakeTouch.framework */; };
21 | 13E77D1D220ACE4700DC0D7D /* FakeTouch.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 13E77CE2220AC59D00DC0D7D /* FakeTouch.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
22 | 13E77D23220B262600DC0D7D /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E77D22220B262600DC0D7D /* WebViewController.swift */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXContainerItemProxy section */
26 | 13E77CE1220AC59D00DC0D7D /* PBXContainerItemProxy */ = {
27 | isa = PBXContainerItemProxy;
28 | containerPortal = 13E77CDD220AC59D00DC0D7D /* FakeTouch.xcodeproj */;
29 | proxyType = 2;
30 | remoteGlobalIDString = 13E77CB2220AC34000DC0D7D;
31 | remoteInfo = FakeTouch;
32 | };
33 | 13E77D1E220ACE4700DC0D7D /* PBXContainerItemProxy */ = {
34 | isa = PBXContainerItemProxy;
35 | containerPortal = 13E77CDD220AC59D00DC0D7D /* FakeTouch.xcodeproj */;
36 | proxyType = 1;
37 | remoteGlobalIDString = 13E77CB1220AC34000DC0D7D;
38 | remoteInfo = FakeTouch;
39 | };
40 | /* End PBXContainerItemProxy section */
41 |
42 | /* Begin PBXCopyFilesBuildPhase section */
43 | 13E77D20220ACE4700DC0D7D /* Embed Frameworks */ = {
44 | isa = PBXCopyFilesBuildPhase;
45 | buildActionMask = 2147483647;
46 | dstPath = "";
47 | dstSubfolderSpec = 10;
48 | files = (
49 | 13E77D1D220ACE4700DC0D7D /* FakeTouch.framework in Embed Frameworks */,
50 | );
51 | name = "Embed Frameworks";
52 | runOnlyForDeploymentPostprocessing = 0;
53 | };
54 | /* End PBXCopyFilesBuildPhase section */
55 |
56 | /* Begin PBXFileReference section */
57 | 1386A530220C820200E72CE6 /* AlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = ""; };
58 | 1386A532220C861C00E72CE6 /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; };
59 | 1386A55A220D7C1E00E72CE6 /* ControlsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlsViewController.swift; sourceTree = ""; };
60 | 13E77CC6220AC38700DC0D7D /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
61 | 13E77CC9220AC38700DC0D7D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
62 | 13E77CCE220AC38700DC0D7D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
63 | 13E77CD0220AC38B00DC0D7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
64 | 13E77CD3220AC38B00DC0D7D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
65 | 13E77CD5220AC38B00DC0D7D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
66 | 13E77CDD220AC59D00DC0D7D /* FakeTouch.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FakeTouch.xcodeproj; path = ../FakeTouch.xcodeproj; sourceTree = ""; };
67 | 13E77D15220ACC2F00DC0D7D /* OverlayWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayWindow.swift; sourceTree = ""; };
68 | 13E77D17220ACC5A00DC0D7D /* OverlayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayViewController.swift; sourceTree = ""; };
69 | 13E77D19220ACD4F00DC0D7D /* Overlay.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Overlay.storyboard; sourceTree = ""; };
70 | 13E77D22220B262600DC0D7D /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; };
71 | /* End PBXFileReference section */
72 |
73 | /* Begin PBXFrameworksBuildPhase section */
74 | 13E77CC3220AC38700DC0D7D /* Frameworks */ = {
75 | isa = PBXFrameworksBuildPhase;
76 | buildActionMask = 2147483647;
77 | files = (
78 | 13E77D1C220ACE4700DC0D7D /* FakeTouch.framework in Frameworks */,
79 | );
80 | runOnlyForDeploymentPostprocessing = 0;
81 | };
82 | /* End PBXFrameworksBuildPhase section */
83 |
84 | /* Begin PBXGroup section */
85 | 13E77CBD220AC38700DC0D7D = {
86 | isa = PBXGroup;
87 | children = (
88 | 13E77CC8220AC38700DC0D7D /* Source */,
89 | 13E77CC7220AC38700DC0D7D /* Products */,
90 | 13E77CDD220AC59D00DC0D7D /* FakeTouch.xcodeproj */,
91 | );
92 | sourceTree = "";
93 | };
94 | 13E77CC7220AC38700DC0D7D /* Products */ = {
95 | isa = PBXGroup;
96 | children = (
97 | 13E77CC6220AC38700DC0D7D /* iOS Example.app */,
98 | );
99 | name = Products;
100 | sourceTree = "";
101 | };
102 | 13E77CC8220AC38700DC0D7D /* Source */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 13E77CC9220AC38700DC0D7D /* AppDelegate.swift */,
106 | 13E77CDC220AC3B800DC0D7D /* Modules */,
107 | 13E77CDB220AC3AC00DC0D7D /* Supporting Files */,
108 | );
109 | path = Source;
110 | sourceTree = "";
111 | };
112 | 13E77CDB220AC3AC00DC0D7D /* Supporting Files */ = {
113 | isa = PBXGroup;
114 | children = (
115 | 13E77CD0220AC38B00DC0D7D /* Assets.xcassets */,
116 | 13E77CD2220AC38B00DC0D7D /* LaunchScreen.storyboard */,
117 | 13E77CD5220AC38B00DC0D7D /* Info.plist */,
118 | );
119 | name = "Supporting Files";
120 | sourceTree = "";
121 | };
122 | 13E77CDC220AC3B800DC0D7D /* Modules */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 13E77CCD220AC38700DC0D7D /* Main.storyboard */,
126 | 1386A532220C861C00E72CE6 /* MenuViewController.swift */,
127 | 1386A55A220D7C1E00E72CE6 /* ControlsViewController.swift */,
128 | 1386A530220C820200E72CE6 /* AlertViewController.swift */,
129 | 13E77D22220B262600DC0D7D /* WebViewController.swift */,
130 | 13E77D1B220ACD5800DC0D7D /* Overlay */,
131 | );
132 | name = Modules;
133 | sourceTree = "";
134 | };
135 | 13E77CDE220AC59D00DC0D7D /* Products */ = {
136 | isa = PBXGroup;
137 | children = (
138 | 13E77CE2220AC59D00DC0D7D /* FakeTouch.framework */,
139 | );
140 | name = Products;
141 | sourceTree = "";
142 | };
143 | 13E77D1B220ACD5800DC0D7D /* Overlay */ = {
144 | isa = PBXGroup;
145 | children = (
146 | 13E77D19220ACD4F00DC0D7D /* Overlay.storyboard */,
147 | 13E77D15220ACC2F00DC0D7D /* OverlayWindow.swift */,
148 | 13E77D17220ACC5A00DC0D7D /* OverlayViewController.swift */,
149 | );
150 | name = Overlay;
151 | sourceTree = "";
152 | };
153 | /* End PBXGroup section */
154 |
155 | /* Begin PBXNativeTarget section */
156 | 13E77CC5220AC38700DC0D7D /* iOS Example */ = {
157 | isa = PBXNativeTarget;
158 | buildConfigurationList = 13E77CD8220AC38B00DC0D7D /* Build configuration list for PBXNativeTarget "iOS Example" */;
159 | buildPhases = (
160 | 13E77CC2220AC38700DC0D7D /* Sources */,
161 | 13E77CC3220AC38700DC0D7D /* Frameworks */,
162 | 13E77CC4220AC38700DC0D7D /* Resources */,
163 | 13E77D20220ACE4700DC0D7D /* Embed Frameworks */,
164 | );
165 | buildRules = (
166 | );
167 | dependencies = (
168 | 13E77D1F220ACE4700DC0D7D /* PBXTargetDependency */,
169 | );
170 | name = "iOS Example";
171 | productName = "iOS Example";
172 | productReference = 13E77CC6220AC38700DC0D7D /* iOS Example.app */;
173 | productType = "com.apple.product-type.application";
174 | };
175 | /* End PBXNativeTarget section */
176 |
177 | /* Begin PBXProject section */
178 | 13E77CBE220AC38700DC0D7D /* Project object */ = {
179 | isa = PBXProject;
180 | attributes = {
181 | LastSwiftUpdateCheck = 1010;
182 | LastUpgradeCheck = 1010;
183 | ORGANIZATIONNAME = "Watanabe Toshinori";
184 | TargetAttributes = {
185 | 13E77CC5220AC38700DC0D7D = {
186 | CreatedOnToolsVersion = 10.1;
187 | };
188 | };
189 | };
190 | buildConfigurationList = 13E77CC1220AC38700DC0D7D /* Build configuration list for PBXProject "iOS Example" */;
191 | compatibilityVersion = "Xcode 9.3";
192 | developmentRegion = en;
193 | hasScannedForEncodings = 0;
194 | knownRegions = (
195 | en,
196 | Base,
197 | );
198 | mainGroup = 13E77CBD220AC38700DC0D7D;
199 | productRefGroup = 13E77CC7220AC38700DC0D7D /* Products */;
200 | projectDirPath = "";
201 | projectReferences = (
202 | {
203 | ProductGroup = 13E77CDE220AC59D00DC0D7D /* Products */;
204 | ProjectRef = 13E77CDD220AC59D00DC0D7D /* FakeTouch.xcodeproj */;
205 | },
206 | );
207 | projectRoot = "";
208 | targets = (
209 | 13E77CC5220AC38700DC0D7D /* iOS Example */,
210 | );
211 | };
212 | /* End PBXProject section */
213 |
214 | /* Begin PBXReferenceProxy section */
215 | 13E77CE2220AC59D00DC0D7D /* FakeTouch.framework */ = {
216 | isa = PBXReferenceProxy;
217 | fileType = wrapper.framework;
218 | path = FakeTouch.framework;
219 | remoteRef = 13E77CE1220AC59D00DC0D7D /* PBXContainerItemProxy */;
220 | sourceTree = BUILT_PRODUCTS_DIR;
221 | };
222 | /* End PBXReferenceProxy section */
223 |
224 | /* Begin PBXResourcesBuildPhase section */
225 | 13E77CC4220AC38700DC0D7D /* Resources */ = {
226 | isa = PBXResourcesBuildPhase;
227 | buildActionMask = 2147483647;
228 | files = (
229 | 13E77CD4220AC38B00DC0D7D /* LaunchScreen.storyboard in Resources */,
230 | 13E77CD1220AC38B00DC0D7D /* Assets.xcassets in Resources */,
231 | 13E77CCF220AC38700DC0D7D /* Main.storyboard in Resources */,
232 | 13E77D1A220ACD4F00DC0D7D /* Overlay.storyboard in Resources */,
233 | );
234 | runOnlyForDeploymentPostprocessing = 0;
235 | };
236 | /* End PBXResourcesBuildPhase section */
237 |
238 | /* Begin PBXSourcesBuildPhase section */
239 | 13E77CC2220AC38700DC0D7D /* Sources */ = {
240 | isa = PBXSourcesBuildPhase;
241 | buildActionMask = 2147483647;
242 | files = (
243 | 13E77D16220ACC2F00DC0D7D /* OverlayWindow.swift in Sources */,
244 | 1386A533220C861C00E72CE6 /* MenuViewController.swift in Sources */,
245 | 13E77CCA220AC38700DC0D7D /* AppDelegate.swift in Sources */,
246 | 1386A55B220D7C1E00E72CE6 /* ControlsViewController.swift in Sources */,
247 | 13E77D18220ACC5A00DC0D7D /* OverlayViewController.swift in Sources */,
248 | 1386A531220C820200E72CE6 /* AlertViewController.swift in Sources */,
249 | 13E77D23220B262600DC0D7D /* WebViewController.swift in Sources */,
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | };
253 | /* End PBXSourcesBuildPhase section */
254 |
255 | /* Begin PBXTargetDependency section */
256 | 13E77D1F220ACE4700DC0D7D /* PBXTargetDependency */ = {
257 | isa = PBXTargetDependency;
258 | name = FakeTouch;
259 | targetProxy = 13E77D1E220ACE4700DC0D7D /* PBXContainerItemProxy */;
260 | };
261 | /* End PBXTargetDependency section */
262 |
263 | /* Begin PBXVariantGroup section */
264 | 13E77CCD220AC38700DC0D7D /* Main.storyboard */ = {
265 | isa = PBXVariantGroup;
266 | children = (
267 | 13E77CCE220AC38700DC0D7D /* Base */,
268 | );
269 | name = Main.storyboard;
270 | sourceTree = "";
271 | };
272 | 13E77CD2220AC38B00DC0D7D /* LaunchScreen.storyboard */ = {
273 | isa = PBXVariantGroup;
274 | children = (
275 | 13E77CD3220AC38B00DC0D7D /* Base */,
276 | );
277 | name = LaunchScreen.storyboard;
278 | sourceTree = "";
279 | };
280 | /* End PBXVariantGroup section */
281 |
282 | /* Begin XCBuildConfiguration section */
283 | 13E77CD6220AC38B00DC0D7D /* Debug */ = {
284 | isa = XCBuildConfiguration;
285 | buildSettings = {
286 | ALWAYS_SEARCH_USER_PATHS = NO;
287 | CLANG_ANALYZER_NONNULL = YES;
288 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
289 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
290 | CLANG_CXX_LIBRARY = "libc++";
291 | CLANG_ENABLE_MODULES = YES;
292 | CLANG_ENABLE_OBJC_ARC = YES;
293 | CLANG_ENABLE_OBJC_WEAK = YES;
294 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
295 | CLANG_WARN_BOOL_CONVERSION = YES;
296 | CLANG_WARN_COMMA = YES;
297 | CLANG_WARN_CONSTANT_CONVERSION = YES;
298 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
299 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
300 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
301 | CLANG_WARN_EMPTY_BODY = YES;
302 | CLANG_WARN_ENUM_CONVERSION = YES;
303 | CLANG_WARN_INFINITE_RECURSION = YES;
304 | CLANG_WARN_INT_CONVERSION = YES;
305 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
306 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
307 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
308 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
309 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
310 | CLANG_WARN_STRICT_PROTOTYPES = YES;
311 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
312 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
313 | CLANG_WARN_UNREACHABLE_CODE = YES;
314 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
315 | CODE_SIGN_IDENTITY = "iPhone Developer";
316 | COPY_PHASE_STRIP = NO;
317 | DEBUG_INFORMATION_FORMAT = dwarf;
318 | ENABLE_STRICT_OBJC_MSGSEND = YES;
319 | ENABLE_TESTABILITY = YES;
320 | GCC_C_LANGUAGE_STANDARD = gnu11;
321 | GCC_DYNAMIC_NO_PIC = NO;
322 | GCC_NO_COMMON_BLOCKS = YES;
323 | GCC_OPTIMIZATION_LEVEL = 0;
324 | GCC_PREPROCESSOR_DEFINITIONS = (
325 | "DEBUG=1",
326 | "$(inherited)",
327 | );
328 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
329 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
330 | GCC_WARN_UNDECLARED_SELECTOR = YES;
331 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
332 | GCC_WARN_UNUSED_FUNCTION = YES;
333 | GCC_WARN_UNUSED_VARIABLE = YES;
334 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
335 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
336 | MTL_FAST_MATH = YES;
337 | ONLY_ACTIVE_ARCH = YES;
338 | SDKROOT = iphoneos;
339 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
340 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
341 | };
342 | name = Debug;
343 | };
344 | 13E77CD7220AC38B00DC0D7D /* Release */ = {
345 | isa = XCBuildConfiguration;
346 | buildSettings = {
347 | ALWAYS_SEARCH_USER_PATHS = NO;
348 | CLANG_ANALYZER_NONNULL = YES;
349 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
350 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
351 | CLANG_CXX_LIBRARY = "libc++";
352 | CLANG_ENABLE_MODULES = YES;
353 | CLANG_ENABLE_OBJC_ARC = YES;
354 | CLANG_ENABLE_OBJC_WEAK = YES;
355 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
356 | CLANG_WARN_BOOL_CONVERSION = YES;
357 | CLANG_WARN_COMMA = YES;
358 | CLANG_WARN_CONSTANT_CONVERSION = YES;
359 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
360 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
361 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
362 | CLANG_WARN_EMPTY_BODY = YES;
363 | CLANG_WARN_ENUM_CONVERSION = YES;
364 | CLANG_WARN_INFINITE_RECURSION = YES;
365 | CLANG_WARN_INT_CONVERSION = YES;
366 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
367 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
368 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
369 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
370 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
371 | CLANG_WARN_STRICT_PROTOTYPES = YES;
372 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
373 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
374 | CLANG_WARN_UNREACHABLE_CODE = YES;
375 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
376 | CODE_SIGN_IDENTITY = "iPhone Developer";
377 | COPY_PHASE_STRIP = NO;
378 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
379 | ENABLE_NS_ASSERTIONS = NO;
380 | ENABLE_STRICT_OBJC_MSGSEND = YES;
381 | GCC_C_LANGUAGE_STANDARD = gnu11;
382 | GCC_NO_COMMON_BLOCKS = YES;
383 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
384 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
385 | GCC_WARN_UNDECLARED_SELECTOR = YES;
386 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
387 | GCC_WARN_UNUSED_FUNCTION = YES;
388 | GCC_WARN_UNUSED_VARIABLE = YES;
389 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
390 | MTL_ENABLE_DEBUG_INFO = NO;
391 | MTL_FAST_MATH = YES;
392 | SDKROOT = iphoneos;
393 | SWIFT_COMPILATION_MODE = wholemodule;
394 | SWIFT_OPTIMIZATION_LEVEL = "-O";
395 | VALIDATE_PRODUCT = YES;
396 | };
397 | name = Release;
398 | };
399 | 13E77CD9220AC38B00DC0D7D /* Debug */ = {
400 | isa = XCBuildConfiguration;
401 | buildSettings = {
402 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
403 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
404 | CODE_SIGN_STYLE = Automatic;
405 | DEVELOPMENT_TEAM = "";
406 | INFOPLIST_FILE = Source/Info.plist;
407 | LD_RUNPATH_SEARCH_PATHS = (
408 | "$(inherited)",
409 | "@executable_path/Frameworks",
410 | );
411 | PRODUCT_BUNDLE_IDENTIFIER = "com.yourcompany.iOS-Example";
412 | PRODUCT_NAME = "$(TARGET_NAME)";
413 | SWIFT_VERSION = 4.2;
414 | TARGETED_DEVICE_FAMILY = "1,2";
415 | };
416 | name = Debug;
417 | };
418 | 13E77CDA220AC38B00DC0D7D /* Release */ = {
419 | isa = XCBuildConfiguration;
420 | buildSettings = {
421 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
422 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
423 | CODE_SIGN_STYLE = Automatic;
424 | DEVELOPMENT_TEAM = "";
425 | INFOPLIST_FILE = Source/Info.plist;
426 | LD_RUNPATH_SEARCH_PATHS = (
427 | "$(inherited)",
428 | "@executable_path/Frameworks",
429 | );
430 | PRODUCT_BUNDLE_IDENTIFIER = "com.yourcompany.iOS-Example";
431 | PRODUCT_NAME = "$(TARGET_NAME)";
432 | SWIFT_VERSION = 4.2;
433 | TARGETED_DEVICE_FAMILY = "1,2";
434 | };
435 | name = Release;
436 | };
437 | /* End XCBuildConfiguration section */
438 |
439 | /* Begin XCConfigurationList section */
440 | 13E77CC1220AC38700DC0D7D /* Build configuration list for PBXProject "iOS Example" */ = {
441 | isa = XCConfigurationList;
442 | buildConfigurations = (
443 | 13E77CD6220AC38B00DC0D7D /* Debug */,
444 | 13E77CD7220AC38B00DC0D7D /* Release */,
445 | );
446 | defaultConfigurationIsVisible = 0;
447 | defaultConfigurationName = Release;
448 | };
449 | 13E77CD8220AC38B00DC0D7D /* Build configuration list for PBXNativeTarget "iOS Example" */ = {
450 | isa = XCConfigurationList;
451 | buildConfigurations = (
452 | 13E77CD9220AC38B00DC0D7D /* Debug */,
453 | 13E77CDA220AC38B00DC0D7D /* Release */,
454 | );
455 | defaultConfigurationIsVisible = 0;
456 | defaultConfigurationName = Release;
457 | };
458 | /* End XCConfigurationList section */
459 | };
460 | rootObject = 13E77CBE220AC38700DC0D7D /* Project object */;
461 | }
462 |
--------------------------------------------------------------------------------
/FakeTouch.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1386A524220C234A00E72CE6 /* TouchEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1386A523220C234A00E72CE6 /* TouchEvent.swift */; };
11 | 1386A526220C327400E72CE6 /* UIEvent+FakeTouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1386A525220C327400E72CE6 /* UIEvent+FakeTouch.swift */; };
12 | 13E77CB7220AC34000DC0D7D /* FakeTouch.h in Headers */ = {isa = PBXBuildFile; fileRef = 13E77CB5220AC34000DC0D7D /* FakeTouch.h */; settings = {ATTRIBUTES = (Public, ); }; };
13 | 13E77CEB220AC5F200DC0D7D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13E77CEA220AC5F200DC0D7D /* IOKit.framework */; };
14 | 13E77CEF220AC60F00DC0D7D /* IOHIDEvent+KIF.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E77CED220AC60F00DC0D7D /* IOHIDEvent+KIF.m */; };
15 | 13E77CF0220AC60F00DC0D7D /* IOHIDEvent+KIF.h in Headers */ = {isa = PBXBuildFile; fileRef = 13E77CEE220AC60F00DC0D7D /* IOHIDEvent+KIF.h */; settings = {ATTRIBUTES = (Public, ); }; };
16 | 13E77CF5220AC63A00DC0D7D /* UIEvent+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 13E77CF2220AC63A00DC0D7D /* UIEvent+Private.h */; settings = {ATTRIBUTES = (Public, ); }; };
17 | 13E77CF6220AC63A00DC0D7D /* UITouch+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 13E77CF3220AC63A00DC0D7D /* UITouch+Private.h */; settings = {ATTRIBUTES = (Public, ); }; };
18 | 13E77CF7220AC63A00DC0D7D /* UIApplication+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 13E77CF4220AC63A00DC0D7D /* UIApplication+Private.h */; settings = {ATTRIBUTES = (Public, ); }; };
19 | 13E77CFB220AC68100DC0D7D /* TouchRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E77CF8220AC68100DC0D7D /* TouchRecorder.swift */; };
20 | 13E77CFC220AC68100DC0D7D /* TouchPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E77CF9220AC68100DC0D7D /* TouchPlayer.swift */; };
21 | 13E77CFD220AC68100DC0D7D /* Touch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E77CFA220AC68100DC0D7D /* Touch.swift */; };
22 | 13E77CFF220AC6D000DC0D7D /* UITouch+FakeTouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E77CFE220AC6D000DC0D7D /* UITouch+FakeTouch.swift */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXFileReference section */
26 | 1386A523220C234A00E72CE6 /* TouchEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchEvent.swift; sourceTree = ""; };
27 | 1386A525220C327400E72CE6 /* UIEvent+FakeTouch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEvent+FakeTouch.swift"; sourceTree = ""; };
28 | 13E77CB2220AC34000DC0D7D /* FakeTouch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FakeTouch.framework; sourceTree = BUILT_PRODUCTS_DIR; };
29 | 13E77CB5220AC34000DC0D7D /* FakeTouch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FakeTouch.h; sourceTree = ""; };
30 | 13E77CB6220AC34000DC0D7D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
31 | 13E77CEA220AC5F200DC0D7D /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/IOKit.framework; sourceTree = DEVELOPER_DIR; };
32 | 13E77CED220AC60F00DC0D7D /* IOHIDEvent+KIF.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "IOHIDEvent+KIF.m"; sourceTree = ""; };
33 | 13E77CEE220AC60F00DC0D7D /* IOHIDEvent+KIF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IOHIDEvent+KIF.h"; sourceTree = ""; };
34 | 13E77CF2220AC63A00DC0D7D /* UIEvent+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIEvent+Private.h"; sourceTree = ""; };
35 | 13E77CF3220AC63A00DC0D7D /* UITouch+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITouch+Private.h"; sourceTree = ""; };
36 | 13E77CF4220AC63A00DC0D7D /* UIApplication+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIApplication+Private.h"; sourceTree = ""; };
37 | 13E77CF8220AC68100DC0D7D /* TouchRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchRecorder.swift; sourceTree = ""; };
38 | 13E77CF9220AC68100DC0D7D /* TouchPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchPlayer.swift; sourceTree = ""; };
39 | 13E77CFA220AC68100DC0D7D /* Touch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Touch.swift; sourceTree = ""; };
40 | 13E77CFE220AC6D000DC0D7D /* UITouch+FakeTouch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITouch+FakeTouch.swift"; sourceTree = ""; };
41 | 13E77D04220ACA7800DC0D7D /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
42 | 13E77D05220ACA7800DC0D7D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
43 | 13E77D0F220ACACE00DC0D7D /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
44 | 13E77D10220ACACE00DC0D7D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 13E77CAF220AC34000DC0D7D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | 13E77CEB220AC5F200DC0D7D /* IOKit.framework in Frameworks */,
53 | );
54 | runOnlyForDeploymentPostprocessing = 0;
55 | };
56 | /* End PBXFrameworksBuildPhase section */
57 |
58 | /* Begin PBXGroup section */
59 | 13AFD1A02256DF2E0077197F /* Additional */ = {
60 | isa = PBXGroup;
61 | children = (
62 | 13E77CFA220AC68100DC0D7D /* Touch.swift */,
63 | 1386A523220C234A00E72CE6 /* TouchEvent.swift */,
64 | 13E77CF8220AC68100DC0D7D /* TouchRecorder.swift */,
65 | 13E77CF9220AC68100DC0D7D /* TouchPlayer.swift */,
66 | );
67 | path = Additional;
68 | sourceTree = "";
69 | };
70 | 13E77CA8220AC34000DC0D7D = {
71 | isa = PBXGroup;
72 | children = (
73 | 13E77D0A220ACAAC00DC0D7D /* Deployment */,
74 | 13E77D08220ACA9400DC0D7D /* Documents */,
75 | 13E77CB4220AC34000DC0D7D /* Source */,
76 | 13E77CB3220AC34000DC0D7D /* Products */,
77 | 13E77CE9220AC5F200DC0D7D /* Frameworks */,
78 | );
79 | sourceTree = "";
80 | };
81 | 13E77CB3220AC34000DC0D7D /* Products */ = {
82 | isa = PBXGroup;
83 | children = (
84 | 13E77CB2220AC34000DC0D7D /* FakeTouch.framework */,
85 | );
86 | name = Products;
87 | sourceTree = "";
88 | };
89 | 13E77CB4220AC34000DC0D7D /* Source */ = {
90 | isa = PBXGroup;
91 | children = (
92 | 13E77CB5220AC34000DC0D7D /* FakeTouch.h */,
93 | 13E77CFE220AC6D000DC0D7D /* UITouch+FakeTouch.swift */,
94 | 1386A525220C327400E72CE6 /* UIEvent+FakeTouch.swift */,
95 | 13E77CF1220AC62E00DC0D7D /* Private Headers */,
96 | 13E77CEC220AC60400DC0D7D /* KIF */,
97 | 13AFD1A02256DF2E0077197F /* Additional */,
98 | 13E77D13220ACB3B00DC0D7D /* Supporting Files */,
99 | );
100 | path = Source;
101 | sourceTree = "";
102 | };
103 | 13E77CE9220AC5F200DC0D7D /* Frameworks */ = {
104 | isa = PBXGroup;
105 | children = (
106 | 13E77CEA220AC5F200DC0D7D /* IOKit.framework */,
107 | );
108 | name = Frameworks;
109 | sourceTree = "";
110 | };
111 | 13E77CEC220AC60400DC0D7D /* KIF */ = {
112 | isa = PBXGroup;
113 | children = (
114 | 13E77CEE220AC60F00DC0D7D /* IOHIDEvent+KIF.h */,
115 | 13E77CED220AC60F00DC0D7D /* IOHIDEvent+KIF.m */,
116 | 13E77D05220ACA7800DC0D7D /* README.md */,
117 | 13E77D04220ACA7800DC0D7D /* LICENSE */,
118 | );
119 | path = KIF;
120 | sourceTree = "";
121 | };
122 | 13E77CF1220AC62E00DC0D7D /* Private Headers */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 13E77CF4220AC63A00DC0D7D /* UIApplication+Private.h */,
126 | 13E77CF2220AC63A00DC0D7D /* UIEvent+Private.h */,
127 | 13E77CF3220AC63A00DC0D7D /* UITouch+Private.h */,
128 | );
129 | path = "Private Headers";
130 | sourceTree = "";
131 | };
132 | 13E77D08220ACA9400DC0D7D /* Documents */ = {
133 | isa = PBXGroup;
134 | children = (
135 | 13E77D10220ACACE00DC0D7D /* README.md */,
136 | 13E77D0F220ACACE00DC0D7D /* LICENSE */,
137 | );
138 | name = Documents;
139 | sourceTree = "";
140 | };
141 | 13E77D0A220ACAAC00DC0D7D /* Deployment */ = {
142 | isa = PBXGroup;
143 | children = (
144 | );
145 | name = Deployment;
146 | sourceTree = "";
147 | };
148 | 13E77D13220ACB3B00DC0D7D /* Supporting Files */ = {
149 | isa = PBXGroup;
150 | children = (
151 | 13E77CB6220AC34000DC0D7D /* Info.plist */,
152 | );
153 | name = "Supporting Files";
154 | sourceTree = "";
155 | };
156 | /* End PBXGroup section */
157 |
158 | /* Begin PBXHeadersBuildPhase section */
159 | 13E77CAD220AC34000DC0D7D /* Headers */ = {
160 | isa = PBXHeadersBuildPhase;
161 | buildActionMask = 2147483647;
162 | files = (
163 | 13E77CF7220AC63A00DC0D7D /* UIApplication+Private.h in Headers */,
164 | 13E77CF0220AC60F00DC0D7D /* IOHIDEvent+KIF.h in Headers */,
165 | 13E77CB7220AC34000DC0D7D /* FakeTouch.h in Headers */,
166 | 13E77CF6220AC63A00DC0D7D /* UITouch+Private.h in Headers */,
167 | 13E77CF5220AC63A00DC0D7D /* UIEvent+Private.h in Headers */,
168 | );
169 | runOnlyForDeploymentPostprocessing = 0;
170 | };
171 | /* End PBXHeadersBuildPhase section */
172 |
173 | /* Begin PBXNativeTarget section */
174 | 13E77CB1220AC34000DC0D7D /* FakeTouch */ = {
175 | isa = PBXNativeTarget;
176 | buildConfigurationList = 13E77CBA220AC34000DC0D7D /* Build configuration list for PBXNativeTarget "FakeTouch" */;
177 | buildPhases = (
178 | 13E77CAD220AC34000DC0D7D /* Headers */,
179 | 13E77CAE220AC34000DC0D7D /* Sources */,
180 | 13E77CAF220AC34000DC0D7D /* Frameworks */,
181 | 13E77CB0220AC34000DC0D7D /* Resources */,
182 | );
183 | buildRules = (
184 | );
185 | dependencies = (
186 | );
187 | name = FakeTouch;
188 | productName = FakeTouch;
189 | productReference = 13E77CB2220AC34000DC0D7D /* FakeTouch.framework */;
190 | productType = "com.apple.product-type.framework";
191 | };
192 | /* End PBXNativeTarget section */
193 |
194 | /* Begin PBXProject section */
195 | 13E77CA9220AC34000DC0D7D /* Project object */ = {
196 | isa = PBXProject;
197 | attributes = {
198 | LastUpgradeCheck = 1010;
199 | ORGANIZATIONNAME = "Watanabe Toshinori";
200 | TargetAttributes = {
201 | 13E77CB1220AC34000DC0D7D = {
202 | CreatedOnToolsVersion = 10.1;
203 | LastSwiftMigration = 1010;
204 | };
205 | };
206 | };
207 | buildConfigurationList = 13E77CAC220AC34000DC0D7D /* Build configuration list for PBXProject "FakeTouch" */;
208 | compatibilityVersion = "Xcode 9.3";
209 | developmentRegion = en;
210 | hasScannedForEncodings = 0;
211 | knownRegions = (
212 | en,
213 | );
214 | mainGroup = 13E77CA8220AC34000DC0D7D;
215 | productRefGroup = 13E77CB3220AC34000DC0D7D /* Products */;
216 | projectDirPath = "";
217 | projectRoot = "";
218 | targets = (
219 | 13E77CB1220AC34000DC0D7D /* FakeTouch */,
220 | );
221 | };
222 | /* End PBXProject section */
223 |
224 | /* Begin PBXResourcesBuildPhase section */
225 | 13E77CB0220AC34000DC0D7D /* Resources */ = {
226 | isa = PBXResourcesBuildPhase;
227 | buildActionMask = 2147483647;
228 | files = (
229 | );
230 | runOnlyForDeploymentPostprocessing = 0;
231 | };
232 | /* End PBXResourcesBuildPhase section */
233 |
234 | /* Begin PBXSourcesBuildPhase section */
235 | 13E77CAE220AC34000DC0D7D /* Sources */ = {
236 | isa = PBXSourcesBuildPhase;
237 | buildActionMask = 2147483647;
238 | files = (
239 | 13E77CFC220AC68100DC0D7D /* TouchPlayer.swift in Sources */,
240 | 13E77CFF220AC6D000DC0D7D /* UITouch+FakeTouch.swift in Sources */,
241 | 13E77CFD220AC68100DC0D7D /* Touch.swift in Sources */,
242 | 13E77CEF220AC60F00DC0D7D /* IOHIDEvent+KIF.m in Sources */,
243 | 1386A524220C234A00E72CE6 /* TouchEvent.swift in Sources */,
244 | 1386A526220C327400E72CE6 /* UIEvent+FakeTouch.swift in Sources */,
245 | 13E77CFB220AC68100DC0D7D /* TouchRecorder.swift in Sources */,
246 | );
247 | runOnlyForDeploymentPostprocessing = 0;
248 | };
249 | /* End PBXSourcesBuildPhase section */
250 |
251 | /* Begin XCBuildConfiguration section */
252 | 13E77CB8220AC34000DC0D7D /* Debug */ = {
253 | isa = XCBuildConfiguration;
254 | buildSettings = {
255 | ALWAYS_SEARCH_USER_PATHS = NO;
256 | CLANG_ANALYZER_NONNULL = YES;
257 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
259 | CLANG_CXX_LIBRARY = "libc++";
260 | CLANG_ENABLE_MODULES = YES;
261 | CLANG_ENABLE_OBJC_ARC = YES;
262 | CLANG_ENABLE_OBJC_WEAK = YES;
263 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
264 | CLANG_WARN_BOOL_CONVERSION = YES;
265 | CLANG_WARN_COMMA = YES;
266 | CLANG_WARN_CONSTANT_CONVERSION = YES;
267 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
269 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
270 | CLANG_WARN_EMPTY_BODY = YES;
271 | CLANG_WARN_ENUM_CONVERSION = YES;
272 | CLANG_WARN_INFINITE_RECURSION = YES;
273 | CLANG_WARN_INT_CONVERSION = YES;
274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
275 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
278 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
279 | CLANG_WARN_STRICT_PROTOTYPES = YES;
280 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
281 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
282 | CLANG_WARN_UNREACHABLE_CODE = YES;
283 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
284 | CODE_SIGN_IDENTITY = "iPhone Developer";
285 | COPY_PHASE_STRIP = NO;
286 | CURRENT_PROJECT_VERSION = 1;
287 | DEBUG_INFORMATION_FORMAT = dwarf;
288 | DEFINES_MODULE = NO;
289 | ENABLE_STRICT_OBJC_MSGSEND = YES;
290 | ENABLE_TESTABILITY = YES;
291 | GCC_C_LANGUAGE_STANDARD = gnu11;
292 | GCC_DYNAMIC_NO_PIC = NO;
293 | GCC_NO_COMMON_BLOCKS = YES;
294 | GCC_OPTIMIZATION_LEVEL = 0;
295 | GCC_PREPROCESSOR_DEFINITIONS = (
296 | "DEBUG=1",
297 | "$(inherited)",
298 | );
299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
301 | GCC_WARN_UNDECLARED_SELECTOR = YES;
302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
303 | GCC_WARN_UNUSED_FUNCTION = YES;
304 | GCC_WARN_UNUSED_VARIABLE = YES;
305 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
306 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
307 | MTL_FAST_MATH = YES;
308 | ONLY_ACTIVE_ARCH = YES;
309 | SDKROOT = iphoneos;
310 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
311 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
312 | VERSIONING_SYSTEM = "apple-generic";
313 | VERSION_INFO_PREFIX = "";
314 | };
315 | name = Debug;
316 | };
317 | 13E77CB9220AC34000DC0D7D /* Release */ = {
318 | isa = XCBuildConfiguration;
319 | buildSettings = {
320 | ALWAYS_SEARCH_USER_PATHS = NO;
321 | CLANG_ANALYZER_NONNULL = YES;
322 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
324 | CLANG_CXX_LIBRARY = "libc++";
325 | CLANG_ENABLE_MODULES = YES;
326 | CLANG_ENABLE_OBJC_ARC = YES;
327 | CLANG_ENABLE_OBJC_WEAK = YES;
328 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
329 | CLANG_WARN_BOOL_CONVERSION = YES;
330 | CLANG_WARN_COMMA = YES;
331 | CLANG_WARN_CONSTANT_CONVERSION = YES;
332 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
334 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
335 | CLANG_WARN_EMPTY_BODY = YES;
336 | CLANG_WARN_ENUM_CONVERSION = YES;
337 | CLANG_WARN_INFINITE_RECURSION = YES;
338 | CLANG_WARN_INT_CONVERSION = YES;
339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
344 | CLANG_WARN_STRICT_PROTOTYPES = YES;
345 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
346 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
347 | CLANG_WARN_UNREACHABLE_CODE = YES;
348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
349 | CODE_SIGN_IDENTITY = "iPhone Developer";
350 | COPY_PHASE_STRIP = NO;
351 | CURRENT_PROJECT_VERSION = 1;
352 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
353 | DEFINES_MODULE = NO;
354 | ENABLE_NS_ASSERTIONS = NO;
355 | ENABLE_STRICT_OBJC_MSGSEND = YES;
356 | GCC_C_LANGUAGE_STANDARD = gnu11;
357 | GCC_NO_COMMON_BLOCKS = YES;
358 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
359 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
360 | GCC_WARN_UNDECLARED_SELECTOR = YES;
361 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
362 | GCC_WARN_UNUSED_FUNCTION = YES;
363 | GCC_WARN_UNUSED_VARIABLE = YES;
364 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
365 | MTL_ENABLE_DEBUG_INFO = NO;
366 | MTL_FAST_MATH = YES;
367 | SDKROOT = iphoneos;
368 | SWIFT_COMPILATION_MODE = wholemodule;
369 | SWIFT_OPTIMIZATION_LEVEL = "-O";
370 | VALIDATE_PRODUCT = YES;
371 | VERSIONING_SYSTEM = "apple-generic";
372 | VERSION_INFO_PREFIX = "";
373 | };
374 | name = Release;
375 | };
376 | 13E77CBB220AC34000DC0D7D /* Debug */ = {
377 | isa = XCBuildConfiguration;
378 | buildSettings = {
379 | CLANG_ENABLE_MODULES = YES;
380 | CODE_SIGN_IDENTITY = "";
381 | CODE_SIGN_STYLE = Automatic;
382 | DEFINES_MODULE = YES;
383 | DYLIB_COMPATIBILITY_VERSION = 1;
384 | DYLIB_CURRENT_VERSION = 1;
385 | DYLIB_INSTALL_NAME_BASE = "@rpath";
386 | INFOPLIST_FILE = Source/Info.plist;
387 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
388 | LD_RUNPATH_SEARCH_PATHS = (
389 | "$(inherited)",
390 | "@executable_path/Frameworks",
391 | "@loader_path/Frameworks",
392 | );
393 | PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.FakeTouch;
394 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
395 | SKIP_INSTALL = YES;
396 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
397 | SWIFT_VERSION = 4.2;
398 | TARGETED_DEVICE_FAMILY = "1,2";
399 | };
400 | name = Debug;
401 | };
402 | 13E77CBC220AC34000DC0D7D /* Release */ = {
403 | isa = XCBuildConfiguration;
404 | buildSettings = {
405 | CLANG_ENABLE_MODULES = YES;
406 | CODE_SIGN_IDENTITY = "";
407 | CODE_SIGN_STYLE = Automatic;
408 | DEFINES_MODULE = YES;
409 | DYLIB_COMPATIBILITY_VERSION = 1;
410 | DYLIB_CURRENT_VERSION = 1;
411 | DYLIB_INSTALL_NAME_BASE = "@rpath";
412 | INFOPLIST_FILE = Source/Info.plist;
413 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
414 | LD_RUNPATH_SEARCH_PATHS = (
415 | "$(inherited)",
416 | "@executable_path/Frameworks",
417 | "@loader_path/Frameworks",
418 | );
419 | PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.FakeTouch;
420 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
421 | SKIP_INSTALL = YES;
422 | SWIFT_VERSION = 4.2;
423 | TARGETED_DEVICE_FAMILY = "1,2";
424 | };
425 | name = Release;
426 | };
427 | /* End XCBuildConfiguration section */
428 |
429 | /* Begin XCConfigurationList section */
430 | 13E77CAC220AC34000DC0D7D /* Build configuration list for PBXProject "FakeTouch" */ = {
431 | isa = XCConfigurationList;
432 | buildConfigurations = (
433 | 13E77CB8220AC34000DC0D7D /* Debug */,
434 | 13E77CB9220AC34000DC0D7D /* Release */,
435 | );
436 | defaultConfigurationIsVisible = 0;
437 | defaultConfigurationName = Release;
438 | };
439 | 13E77CBA220AC34000DC0D7D /* Build configuration list for PBXNativeTarget "FakeTouch" */ = {
440 | isa = XCConfigurationList;
441 | buildConfigurations = (
442 | 13E77CBB220AC34000DC0D7D /* Debug */,
443 | 13E77CBC220AC34000DC0D7D /* Release */,
444 | );
445 | defaultConfigurationIsVisible = 0;
446 | defaultConfigurationName = Release;
447 | };
448 | /* End XCConfigurationList section */
449 | };
450 | rootObject = 13E77CA9220AC34000DC0D7D /* Project object */;
451 | }
452 |
--------------------------------------------------------------------------------
/Example/Source/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
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 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
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 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
--------------------------------------------------------------------------------