├── 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 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 | ![Preview1](Preview/Preview1.png) 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 | [![Build Status](https://travis-ci.org/kif-framework/KIF.svg?branch=master)](https://travis-ci.org/kif-framework/KIF) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPod Version](https://img.shields.io/cocoapods/v/KIF.svg?style=flat)](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 | ![Simple App](https://github.com/kif-framework/KIF/raw/master/Documentation/Images/Simple%20App.png) 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 | ![Added KIF to the project](https://github.com/kif-framework/KIF/raw/master/Documentation/Images/Added%20KIF%20to%20Project.png) 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 | ![Add libKIF library screen shot](https://github.com/kif-framework/KIF/raw/master/Documentation/Images/Add%20Library.png) 102 | 103 | ![Add libKIF library screen shot](https://github.com/kif-framework/KIF/raw/master/Documentation/Images/Add%20Library%20Sheet.png) 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 | ![Add category linker flags screen shot](https://github.com/kif-framework/KIF/raw/master/Documentation/Images/Add%20Category%20Linker%20Flags.png) 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 | --------------------------------------------------------------------------------