├── wyler.png ├── Wyler ├── SampleApp │ ├── MP3Sample.mp3 │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── Main.storyboard ├── Wyler.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj └── Wyler │ ├── Wyler.h │ ├── Info.plist │ └── ScreenRecorder.swift ├── Package.swift ├── Wyler.podspec ├── LICENSE ├── .gitignore └── README.md /wyler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Wyler/HEAD/wyler.png -------------------------------------------------------------------------------- /Wyler/SampleApp/MP3Sample.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Wyler/HEAD/Wyler/SampleApp/MP3Sample.mp3 -------------------------------------------------------------------------------- /Wyler/SampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Wyler/SampleApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Wyler/Wyler.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Wyler/SampleApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SampleApp 4 | // 5 | // Created by Cesar Vargas on 10.04.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, 16 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | /** 4 | * Warhol 5 | * Copyright (c) César Vargas Casaseca 2020 6 | * Licensed under the MIT license (see LICENSE file) 7 | */ 8 | 9 | import PackageDescription 10 | 11 | let package = Package( 12 | name: "Wyler", 13 | platforms: [ 14 | .iOS(.v11)], 15 | products: [ 16 | .library( 17 | name: "Wyler", 18 | targets: ["Wyler"] 19 | ) 20 | ], 21 | targets: [ 22 | .target(name: "Wyler", 23 | path: "Wyler/Wyler") 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Wyler/Wyler/Wyler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Wyler.h 3 | // Wyler 4 | // 5 | // Created by Cesar Vargas on 10.04.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Wyler. 12 | FOUNDATION_EXPORT double WylerVersionNumber; 13 | 14 | //! Project version string for Wyler. 15 | FOUNDATION_EXPORT const unsigned char WylerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Wyler/Wyler/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Wyler.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint Wyler.podspec' to ensure this is a 3 | # valid spec and remove all comments before submitting the spec. 4 | # 5 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 6 | # 7 | 8 | Pod::Spec.new do |s| 9 | s.name = "Wyler" 10 | s.version = "0.1" 11 | s.summary = "Screen Recording Made Easy." 12 | s.description = <<-DESC 13 | A light library written in Swift that makes easy the process of Screen Recording for IOS. You can record your app video screen, access to the recorded video, and save it to the Photo Library. 14 | DESC 15 | s.homepage = "https://github.com/toupper/Wyler" 16 | s.license = 'MIT' 17 | s.author = { "César Vargas Casaseca" => "c.vargas.casaseca@gmail.com" } 18 | s.source = { :git => "https://github.com/toupper/Wyler.git", :tag => s.version.to_s } 19 | 20 | s.ios.deployment_target = '11.0' 21 | 22 | s.requires_arc = true 23 | 24 | s.ios.source_files = 'Wyler/Wyler/**/*.{h,m,swift}' 25 | 26 | s.swift_version = "5.2" 27 | end 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 César Vargas Casaseca 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 | -------------------------------------------------------------------------------- /Wyler/SampleApp/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 | -------------------------------------------------------------------------------- /Wyler/SampleApp/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSPhotoLibraryUsageDescription 24 | Save Sample Video to Camera Roll 25 | NSMicrophoneUsageDescription 26 | Record audio 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile~iphone 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Wyler/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Wyler/SampleApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SampleApp 4 | // 5 | // Created by Cesar Vargas on 12.04.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import QuartzCore 12 | import Wyler 13 | import AVFoundation 14 | 15 | final class ViewController: UIViewController { 16 | @IBOutlet weak var bouncingBall: UIView! 17 | @IBOutlet weak var startRecordingButton: UIButton! 18 | @IBOutlet weak var startRecordingWithoutAudioButton: UIButton! 19 | @IBOutlet weak var stopRecordingButton: UIButton! 20 | @IBOutlet weak var cameraRollSwitch: UISwitch! 21 | @IBOutlet weak var cameraRollLabel: UILabel! 22 | 23 | private var player: AVAudioPlayer? 24 | private let screenRecorder = ScreenRecorder() 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | bouncingBall.layer.cornerRadius = bouncingBall.frame.width / 2 30 | } 31 | 32 | @IBAction func stopRecordingButtonWasPressed(_ sender: Any) { 33 | enableElements(isRecording: false) 34 | 35 | bouncingBall.layer.removeAllAnimations() 36 | self.bouncingBall.alpha = 1 37 | 38 | screenRecorder.stoprecording(errorHandler: { error in 39 | debugPrint("Error when stop recording \(error)") 40 | }) 41 | } 42 | 43 | @IBAction func startRecordingButtonWasPressed(_ sender: Any) { 44 | enableElements(isRecording: true) 45 | 46 | playSound() 47 | animateBall() 48 | 49 | screenRecorder.startRecording(saveToCameraRoll: cameraRollSwitch.isOn, 50 | errorHandler: { error in 51 | debugPrint("Error when recording \(error)") 52 | }) 53 | } 54 | 55 | @IBAction func startRecordingButtonWithoutAudioWasPressed(_ sender: Any) { 56 | enableElements(isRecording: true) 57 | 58 | playSound() 59 | animateBall() 60 | 61 | screenRecorder.startRecording(saveToCameraRoll: cameraRollSwitch.isOn, 62 | recordAudio: false, 63 | errorHandler: { error in 64 | debugPrint("Error when recording \(error)") 65 | }) 66 | } 67 | 68 | private func playSound() { 69 | guard let url = Bundle.main.url(forResource: "MP3Sample", withExtension: "mp3") else { return } 70 | 71 | do { 72 | try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) 73 | try AVAudioSession.sharedInstance().setActive(true) 74 | 75 | player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) 76 | 77 | player?.play() 78 | 79 | } catch let error { 80 | print(error.localizedDescription) 81 | } 82 | } 83 | 84 | private func enableElements(isRecording: Bool) { 85 | startRecordingButton.isEnabled = !isRecording 86 | startRecordingWithoutAudioButton.isEnabled = !isRecording 87 | stopRecordingButton.isEnabled = isRecording 88 | cameraRollSwitch.isEnabled = !isRecording 89 | cameraRollLabel.isEnabled = !isRecording 90 | } 91 | 92 | private func animateBall() { 93 | UIView.animate(withDuration: 2, 94 | delay: 1, 95 | usingSpringWithDamping: 0.5, 96 | initialSpringVelocity: 5, 97 | options: [.curveEaseInOut, .repeat, .autoreverse], animations: { 98 | self.bouncingBall.alpha = 0.25 99 | }, completion: { _ in }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Wyler 3 |

4 | 5 |

6 | 7 | 8 | Carthage 9 | 10 | 11 | CocoaPods 12 | 13 | 14 | PRs Welcome 15 | 16 | 17 | Medium: @toupper 18 | 19 |

20 | 21 | Welcome to **Wyler** — A light library written in Swift that makes easy the process of Screen Recording for IOS. You can record your app video screen, access to the recorded video, and save it to the Photo Library. 22 | 23 | ## Features 24 | 25 | - [x] App Screen Recording 26 | - [x] Set Video Size 27 | - [x] Access to the Video 28 | - [x] Save the video to the Photo Library 29 | 30 | ## Requirements 31 | 32 | - iOS 11.0+ 33 | - Xcode 11.0+ 34 | 35 | ## Installation 36 | Since Wyler is implemented within a single file, the easiest way to use it is to simply drag and drop it into your Xcode project. If anyways you want to use a dependency manager: 37 | 38 | #### CocoaPods 39 | You can use [CocoaPods](http://cocoapods.org/) to install `Wyler` by adding it to your `Podfile`: 40 | 41 | ```ruby 42 | platform :ios, '11.0' 43 | use_frameworks! 44 | pod 'Wyler' 45 | ``` 46 | 47 | To get the full benefits import `Wyler` wherever you use it 48 | 49 | ``` swift 50 | import Wyler 51 | ``` 52 | ### Carthage 53 | 54 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`: 55 | 56 | ```ogdl 57 | github "toupper/Wyler" 58 | ``` 59 | ## Manually 60 | 61 | You can also integrate Wyler into your project manually. 62 | 63 | #### Embedded Framework 64 | 65 | - Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository: 66 | 67 | ```bash 68 | $ git init 69 | ``` 70 | 71 | - Add Wyler as a git [submodule](https://git-scm.com/docs/git-submodule) by running the following command: 72 | 73 | ```bash 74 | $ git submodule add https://github.com/toupper/Wyler.git 75 | ``` 76 | 77 | - Open the new `Wyler` folder, and drag the `Wyler.xcodeproj` into the Project Navigator of your application's Xcode project. 78 | 79 | - And that's it! 80 | 81 | ## Usage example 82 | 83 | ### Recording 84 | 85 | Import Wyler in the file you are going to use it. Create an instance of ```ScreenRecorder```, and call it to start recording whenever you want: 86 | 87 | ```swift 88 | import Wyler 89 | 90 | screenRecorder.startRecording(saveToCameraRoll: true, errorHandler: { error in 91 | debugPrint("Error when recording \(error)") 92 | }) 93 | ``` 94 | If you want to access the video, turn off audio recording, or set a different size than the App screen, you can pass these parameters: 95 | 96 | ```swift 97 | import Wyler 98 | 99 | screenRecorder.startRecording(to: yourInternalURL, 100 | size: yourSize, 101 | saveToCameraRoll: true, 102 | recordAudio: shouldRecordAudio, 103 | errorHandler: { error in 104 | debugPrint("Error when recording \(error)") 105 | }) 106 | ``` 107 | 108 | 109 | When you want to stop recording, you just have to call the recorder with stop recording: 110 | 111 | ```swift 112 | import Wyler 113 | 114 | screenRecorder.stoprecording(errorHandler: { error in 115 | debugPrint("Error when stop recording \(error)") 116 | }) 117 | ``` 118 | If you want to save the video to the camera, do not forget to add the Privacy - Photo Library Usage Description to the Info.plist 119 | ## Contribute 120 | 121 | We would love you for the contribution to **Wyler**, check the ``LICENSE`` file for more info. 122 | 123 | ## Credits 124 | 125 | Created and maintained with love by [César Vargas Casaseca](https://github.com/toupper). You can follow me on Medium [@toupper](https://medium.com/@toupper) for project updates, releases and more stories. 126 | 127 | ## License 128 | 129 | Wyler is released under the MIT license. [See LICENSE](https://github.com/toupper/Wyler/blob/master/LICENSE) for details. 130 | -------------------------------------------------------------------------------- /Wyler/Wyler/ScreenRecorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenRecorder.swift 3 | // Wyler 4 | // 5 | // Created by Cesar Vargas on 10.04.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReplayKit 11 | import Photos 12 | 13 | public enum ScreenRecorderError: Error { 14 | case notAvailable 15 | case photoLibraryAccessNotGranted 16 | } 17 | 18 | final public class ScreenRecorder { 19 | private var videoOutputURL: URL? 20 | private var videoWriter: AVAssetWriter? 21 | private var videoWriterInput: AVAssetWriterInput? 22 | private var micAudioWriterInput: AVAssetWriterInput? 23 | private var appAudioWriterInput: AVAssetWriterInput? 24 | private var saveToCameraRoll = false 25 | private var recordAudio = true 26 | let recorder = RPScreenRecorder.shared() 27 | 28 | /** 29 | Starts recording the content of the application screen. It works together with stopRecording 30 | 31 | - Parameter outputURL: The output where the video will be saved. If nil, it saves it in the documents directory. 32 | - Parameter size: The size of the video. If nil, it will use the app screen size. 33 | - Parameter saveToCameraRoll: Whether to save it to camera roll. False by default. 34 | - Parameter recordAudio: Whether to record audio as well as video. True by default for compatibility. 35 | - Parameter errorHandler: Called when an error is found 36 | */ 37 | public func startRecording(to outputURL: URL? = nil, 38 | size: CGSize? = nil, 39 | saveToCameraRoll: Bool = false, 40 | recordAudio: Bool = true, 41 | errorHandler: @escaping (Error) -> Void) { 42 | do { 43 | try createVideoWriter(in: outputURL) 44 | addVideoWriterInput(size: size) 45 | self.recordAudio = recordAudio 46 | self.recorder.isMicrophoneEnabled = recordAudio 47 | if(recordAudio) { 48 | self.micAudioWriterInput = createAndAddAudioInput() 49 | self.appAudioWriterInput = createAndAddAudioInput() 50 | } 51 | startCapture(error: errorHandler) 52 | } catch let err { 53 | errorHandler(err) 54 | } 55 | } 56 | 57 | private func checkPhotoLibraryAuthorizationStatus() { 58 | let status = PHPhotoLibrary.authorizationStatus() 59 | if status == .notDetermined { 60 | PHPhotoLibrary.requestAuthorization({ _ in }) 61 | } 62 | } 63 | 64 | private func createVideoWriter(in outputURL: URL? = nil) throws { 65 | let newVideoOutputURL: URL 66 | 67 | if let passedVideoOutput = outputURL { 68 | self.videoOutputURL = passedVideoOutput 69 | newVideoOutputURL = passedVideoOutput 70 | } else { 71 | let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString 72 | newVideoOutputURL = URL(fileURLWithPath: documentsPath.appendingPathComponent("WylerNewVideo.mp4")) 73 | self.videoOutputURL = newVideoOutputURL 74 | } 75 | 76 | do { 77 | try FileManager.default.removeItem(at: newVideoOutputURL) 78 | } catch {} 79 | 80 | do { 81 | try videoWriter = AVAssetWriter(outputURL: newVideoOutputURL, fileType: AVFileType.mp4) 82 | } catch let writerError as NSError { 83 | videoWriter = nil 84 | throw writerError 85 | } 86 | } 87 | 88 | private func addVideoWriterInput(size: CGSize?) { 89 | let passingSize: CGSize = size ?? UIScreen.main.bounds.size 90 | 91 | let videoSettings: [String: Any] = [AVVideoCodecKey: AVVideoCodecType.h264, 92 | AVVideoWidthKey: passingSize.width, 93 | AVVideoHeightKey: passingSize.height] 94 | 95 | let newVideoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings) 96 | self.videoWriterInput = newVideoWriterInput 97 | newVideoWriterInput.expectsMediaDataInRealTime = true 98 | videoWriter?.add(newVideoWriterInput) 99 | } 100 | 101 | private func createAndAddAudioInput() -> AVAssetWriterInput { 102 | let settings = [ 103 | AVFormatIDKey: Int(kAudioFormatMPEG4AAC), 104 | AVSampleRateKey: 12000, 105 | AVNumberOfChannelsKey: 1, 106 | AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue 107 | ] 108 | 109 | let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: settings) 110 | 111 | audioInput.expectsMediaDataInRealTime = true 112 | videoWriter?.add(audioInput) 113 | 114 | return audioInput 115 | } 116 | 117 | private func startCapture(handler: @escaping (Error?) -> Void) { 118 | guard recorder.isAvailable else { 119 | return handler(ScreenRecorderError.notAvailable) 120 | } 121 | recorder.startCapture(handler: { (sampleBuffer, sampleType, passedError) in 122 | if let passedError = passedError { 123 | handler(passedError) 124 | } 125 | 126 | switch sampleType { 127 | case .video: 128 | self.handleSampleBuffer(sampleBuffer: sampleBuffer) 129 | case .audioApp: 130 | self.add(sample: sampleBuffer, to: self.appAudioWriterInput) 131 | case .audioMic: 132 | self.add(sample: sampleBuffer, to: self.micAudioWriterInput) 133 | default: 134 | break 135 | } 136 | handler(nil) 137 | }) 138 | } 139 | 140 | private func handleSampleBuffer(sampleBuffer: CMSampleBuffer) { 141 | if self.videoWriter?.status == AVAssetWriter.Status.unknown { 142 | self.videoWriter?.startWriting() 143 | self.videoWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) 144 | } else if self.videoWriter?.status == AVAssetWriter.Status.writing && 145 | self.videoWriterInput?.isReadyForMoreMediaData == true { 146 | self.videoWriterInput?.append(sampleBuffer) 147 | } 148 | } 149 | 150 | private func add(sample: CMSampleBuffer, to writerInput: AVAssetWriterInput?) { 151 | if writerInput?.isReadyForMoreMediaData ?? false { 152 | writerInput?.append(sample) 153 | } 154 | } 155 | 156 | /** 157 | Stops recording the content of the application screen, after calling startRecording 158 | 159 | - Parameter errorHandler: Called when an error is found 160 | */ 161 | public func stoprecording(handler: @escaping (Error?) -> Void) { 162 | recorder.stopCapture( handler: { error in 163 | if let error = error { 164 | handler(error) 165 | } else { 166 | self.videoWriterInput?.markAsFinished() 167 | if(self.recordAudio) { 168 | self.micAudioWriterInput?.markAsFinished() 169 | self.appAudioWriterInput?.markAsFinished() 170 | } 171 | self.videoWriter?.finishWriting { 172 | self.saveVideoToCameraRollAfterAuthorized(handler: handler) 173 | } 174 | } 175 | }) 176 | 177 | self.videoWriterInput?.markAsFinished() 178 | if(self.recordAudio) { 179 | self.micAudioWriterInput?.markAsFinished() 180 | self.appAudioWriterInput?.markAsFinished() 181 | } 182 | self.videoWriter?.finishWriting { 183 | self.saveVideoToCameraRollAfterAuthorized(errorHandler: errorHandler) 184 | } 185 | } 186 | 187 | private func saveVideoToCameraRollAfterAuthorized(handler: @escaping (Error?) -> Void) { 188 | if PHPhotoLibrary.authorizationStatus() == .authorized { 189 | self.saveVideoToCameraRoll(handler: handler) 190 | } else { 191 | PHPhotoLibrary.requestAuthorization({ (status) in 192 | if status == .authorized { 193 | self.saveVideoToCameraRoll(handler: handler) 194 | } else { 195 | handler(ScreenRecorderError.photoLibraryAccessNotGranted) 196 | } 197 | }) 198 | } 199 | } 200 | 201 | private func saveVideoToCameraRoll(handler: @escaping (Error?) -> Void) { 202 | guard let videoOutputURL = self.videoOutputURL else { 203 | return handler(nil) 204 | } 205 | 206 | PHPhotoLibrary.shared().performChanges({ 207 | PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoOutputURL) 208 | }, completionHandler: { _, error in 209 | if let error = error { 210 | handler(error) 211 | } else { 212 | handler(nil) 213 | } 214 | }) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Wyler/SampleApp/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 | 31 | 39 | 40 | 41 | 42 | 48 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /Wyler/Wyler.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 370DA4C62440D8C300BAB2B7 /* Wyler.h in Headers */ = {isa = PBXBuildFile; fileRef = 370DA4C42440D8C300BAB2B7 /* Wyler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 370DA4D32440D8DA00BAB2B7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370DA4D22440D8DA00BAB2B7 /* AppDelegate.swift */; }; 12 | 370DA4D92440D8DE00BAB2B7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 370DA4D82440D8DE00BAB2B7 /* Assets.xcassets */; }; 13 | 370DA4DC2440D8DE00BAB2B7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 370DA4DB2440D8DE00BAB2B7 /* Preview Assets.xcassets */; }; 14 | 370DA4DF2440D8DE00BAB2B7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 370DA4DD2440D8DE00BAB2B7 /* LaunchScreen.storyboard */; }; 15 | 370DA4E52440D96900BAB2B7 /* ScreenRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370DA4E42440D96900BAB2B7 /* ScreenRecorder.swift */; }; 16 | 370DA4E82443678400BAB2B7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 370DA4E72443678400BAB2B7 /* Main.storyboard */; }; 17 | 370DA4EA244367A700BAB2B7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370DA4E9244367A700BAB2B7 /* ViewController.swift */; }; 18 | 370DA4EC2443710500BAB2B7 /* Wyler.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 370DA4C12440D8C300BAB2B7 /* Wyler.framework */; }; 19 | 370DA4ED2443710500BAB2B7 /* Wyler.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 370DA4C12440D8C300BAB2B7 /* Wyler.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 20 | 37B519B7244C71D30048B034 /* MP3Sample.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 37B519B6244C71D30048B034 /* MP3Sample.mp3 */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 370DA4EE2443710500BAB2B7 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 370DA4B82440D8C300BAB2B7 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 370DA4C02440D8C300BAB2B7; 29 | remoteInfo = Wyler; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXCopyFilesBuildPhase section */ 34 | 370DA4F02443710500BAB2B7 /* Embed Frameworks */ = { 35 | isa = PBXCopyFilesBuildPhase; 36 | buildActionMask = 2147483647; 37 | dstPath = ""; 38 | dstSubfolderSpec = 10; 39 | files = ( 40 | 370DA4ED2443710500BAB2B7 /* Wyler.framework in Embed Frameworks */, 41 | ); 42 | name = "Embed Frameworks"; 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXCopyFilesBuildPhase section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 370DA4C12440D8C300BAB2B7 /* Wyler.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Wyler.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 370DA4C42440D8C300BAB2B7 /* Wyler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Wyler.h; sourceTree = ""; }; 50 | 370DA4C52440D8C300BAB2B7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 370DA4D02440D8DA00BAB2B7 /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 370DA4D22440D8DA00BAB2B7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 53 | 370DA4D82440D8DE00BAB2B7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 370DA4DB2440D8DE00BAB2B7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 55 | 370DA4DE2440D8DE00BAB2B7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | 370DA4E02440D8DE00BAB2B7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 370DA4E42440D96900BAB2B7 /* ScreenRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecorder.swift; sourceTree = ""; }; 58 | 370DA4E72443678400BAB2B7 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 59 | 370DA4E9244367A700BAB2B7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 60 | 37B519B6244C71D30048B034 /* MP3Sample.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = MP3Sample.mp3; sourceTree = ""; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | 370DA4BE2440D8C300BAB2B7 /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | 370DA4CD2440D8DA00BAB2B7 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 370DA4EC2443710500BAB2B7 /* Wyler.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXFrameworksBuildPhase section */ 80 | 81 | /* Begin PBXGroup section */ 82 | 370DA4B72440D8C300BAB2B7 = { 83 | isa = PBXGroup; 84 | children = ( 85 | 370DA4C32440D8C300BAB2B7 /* Wyler */, 86 | 370DA4D12440D8DA00BAB2B7 /* SampleApp */, 87 | 370DA4C22440D8C300BAB2B7 /* Products */, 88 | 370DA4EB2443710500BAB2B7 /* Frameworks */, 89 | ); 90 | sourceTree = ""; 91 | }; 92 | 370DA4C22440D8C300BAB2B7 /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 370DA4C12440D8C300BAB2B7 /* Wyler.framework */, 96 | 370DA4D02440D8DA00BAB2B7 /* SampleApp.app */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 370DA4C32440D8C300BAB2B7 /* Wyler */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 370DA4C42440D8C300BAB2B7 /* Wyler.h */, 105 | 370DA4C52440D8C300BAB2B7 /* Info.plist */, 106 | 370DA4E42440D96900BAB2B7 /* ScreenRecorder.swift */, 107 | ); 108 | path = Wyler; 109 | sourceTree = ""; 110 | }; 111 | 370DA4D12440D8DA00BAB2B7 /* SampleApp */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 37B519B6244C71D30048B034 /* MP3Sample.mp3 */, 115 | 370DA4E9244367A700BAB2B7 /* ViewController.swift */, 116 | 370DA4E72443678400BAB2B7 /* Main.storyboard */, 117 | 370DA4D22440D8DA00BAB2B7 /* AppDelegate.swift */, 118 | 370DA4D82440D8DE00BAB2B7 /* Assets.xcassets */, 119 | 370DA4DD2440D8DE00BAB2B7 /* LaunchScreen.storyboard */, 120 | 370DA4E02440D8DE00BAB2B7 /* Info.plist */, 121 | 370DA4DA2440D8DE00BAB2B7 /* Preview Content */, 122 | ); 123 | path = SampleApp; 124 | sourceTree = ""; 125 | }; 126 | 370DA4DA2440D8DE00BAB2B7 /* Preview Content */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 370DA4DB2440D8DE00BAB2B7 /* Preview Assets.xcassets */, 130 | ); 131 | path = "Preview Content"; 132 | sourceTree = ""; 133 | }; 134 | 370DA4EB2443710500BAB2B7 /* Frameworks */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | ); 138 | name = Frameworks; 139 | sourceTree = ""; 140 | }; 141 | /* End PBXGroup section */ 142 | 143 | /* Begin PBXHeadersBuildPhase section */ 144 | 370DA4BC2440D8C300BAB2B7 /* Headers */ = { 145 | isa = PBXHeadersBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | 370DA4C62440D8C300BAB2B7 /* Wyler.h in Headers */, 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | }; 152 | /* End PBXHeadersBuildPhase section */ 153 | 154 | /* Begin PBXNativeTarget section */ 155 | 370DA4C02440D8C300BAB2B7 /* Wyler */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = 370DA4C92440D8C300BAB2B7 /* Build configuration list for PBXNativeTarget "Wyler" */; 158 | buildPhases = ( 159 | 370DA4BC2440D8C300BAB2B7 /* Headers */, 160 | 370DA4E624422F6A00BAB2B7 /* SwiftLint */, 161 | 370DA4BD2440D8C300BAB2B7 /* Sources */, 162 | 370DA4BE2440D8C300BAB2B7 /* Frameworks */, 163 | 370DA4BF2440D8C300BAB2B7 /* Resources */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = Wyler; 170 | productName = Wyler; 171 | productReference = 370DA4C12440D8C300BAB2B7 /* Wyler.framework */; 172 | productType = "com.apple.product-type.framework"; 173 | }; 174 | 370DA4CF2440D8DA00BAB2B7 /* SampleApp */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = 370DA4E12440D8DE00BAB2B7 /* Build configuration list for PBXNativeTarget "SampleApp" */; 177 | buildPhases = ( 178 | 370DA4CC2440D8DA00BAB2B7 /* Sources */, 179 | 370DA4CD2440D8DA00BAB2B7 /* Frameworks */, 180 | 370DA4CE2440D8DA00BAB2B7 /* Resources */, 181 | 370DA4F02443710500BAB2B7 /* Embed Frameworks */, 182 | ); 183 | buildRules = ( 184 | ); 185 | dependencies = ( 186 | 370DA4EF2443710500BAB2B7 /* PBXTargetDependency */, 187 | ); 188 | name = SampleApp; 189 | productName = SampleApp; 190 | productReference = 370DA4D02440D8DA00BAB2B7 /* SampleApp.app */; 191 | productType = "com.apple.product-type.application"; 192 | }; 193 | /* End PBXNativeTarget section */ 194 | 195 | /* Begin PBXProject section */ 196 | 370DA4B82440D8C300BAB2B7 /* Project object */ = { 197 | isa = PBXProject; 198 | attributes = { 199 | LastSwiftUpdateCheck = 1140; 200 | LastUpgradeCheck = 1140; 201 | ORGANIZATIONNAME = "Cesar Vargas"; 202 | TargetAttributes = { 203 | 370DA4C02440D8C300BAB2B7 = { 204 | CreatedOnToolsVersion = 11.4; 205 | LastSwiftMigration = 1140; 206 | }; 207 | 370DA4CF2440D8DA00BAB2B7 = { 208 | CreatedOnToolsVersion = 11.4; 209 | }; 210 | }; 211 | }; 212 | buildConfigurationList = 370DA4BB2440D8C300BAB2B7 /* Build configuration list for PBXProject "Wyler" */; 213 | compatibilityVersion = "Xcode 9.3"; 214 | developmentRegion = en; 215 | hasScannedForEncodings = 0; 216 | knownRegions = ( 217 | en, 218 | Base, 219 | ); 220 | mainGroup = 370DA4B72440D8C300BAB2B7; 221 | productRefGroup = 370DA4C22440D8C300BAB2B7 /* Products */; 222 | projectDirPath = ""; 223 | projectRoot = ""; 224 | targets = ( 225 | 370DA4C02440D8C300BAB2B7 /* Wyler */, 226 | 370DA4CF2440D8DA00BAB2B7 /* SampleApp */, 227 | ); 228 | }; 229 | /* End PBXProject section */ 230 | 231 | /* Begin PBXResourcesBuildPhase section */ 232 | 370DA4BF2440D8C300BAB2B7 /* Resources */ = { 233 | isa = PBXResourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | 370DA4CE2440D8DA00BAB2B7 /* Resources */ = { 240 | isa = PBXResourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 370DA4DF2440D8DE00BAB2B7 /* LaunchScreen.storyboard in Resources */, 244 | 37B519B7244C71D30048B034 /* MP3Sample.mp3 in Resources */, 245 | 370DA4DC2440D8DE00BAB2B7 /* Preview Assets.xcassets in Resources */, 246 | 370DA4D92440D8DE00BAB2B7 /* Assets.xcassets in Resources */, 247 | 370DA4E82443678400BAB2B7 /* Main.storyboard in Resources */, 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | /* End PBXResourcesBuildPhase section */ 252 | 253 | /* Begin PBXShellScriptBuildPhase section */ 254 | 370DA4E624422F6A00BAB2B7 /* SwiftLint */ = { 255 | isa = PBXShellScriptBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | inputFileListPaths = ( 260 | ); 261 | inputPaths = ( 262 | ); 263 | name = SwiftLint; 264 | outputFileListPaths = ( 265 | ); 266 | outputPaths = ( 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | shellPath = /bin/sh; 270 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 271 | }; 272 | /* End PBXShellScriptBuildPhase section */ 273 | 274 | /* Begin PBXSourcesBuildPhase section */ 275 | 370DA4BD2440D8C300BAB2B7 /* Sources */ = { 276 | isa = PBXSourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | 370DA4E52440D96900BAB2B7 /* ScreenRecorder.swift in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | 370DA4CC2440D8DA00BAB2B7 /* Sources */ = { 284 | isa = PBXSourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 370DA4EA244367A700BAB2B7 /* ViewController.swift in Sources */, 288 | 370DA4D32440D8DA00BAB2B7 /* AppDelegate.swift in Sources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | /* End PBXSourcesBuildPhase section */ 293 | 294 | /* Begin PBXTargetDependency section */ 295 | 370DA4EF2443710500BAB2B7 /* PBXTargetDependency */ = { 296 | isa = PBXTargetDependency; 297 | target = 370DA4C02440D8C300BAB2B7 /* Wyler */; 298 | targetProxy = 370DA4EE2443710500BAB2B7 /* PBXContainerItemProxy */; 299 | }; 300 | /* End PBXTargetDependency section */ 301 | 302 | /* Begin PBXVariantGroup section */ 303 | 370DA4DD2440D8DE00BAB2B7 /* LaunchScreen.storyboard */ = { 304 | isa = PBXVariantGroup; 305 | children = ( 306 | 370DA4DE2440D8DE00BAB2B7 /* Base */, 307 | ); 308 | name = LaunchScreen.storyboard; 309 | sourceTree = ""; 310 | }; 311 | /* End PBXVariantGroup section */ 312 | 313 | /* Begin XCBuildConfiguration section */ 314 | 370DA4C72440D8C300BAB2B7 /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ALWAYS_SEARCH_USER_PATHS = NO; 318 | CLANG_ANALYZER_NONNULL = YES; 319 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 320 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 321 | CLANG_CXX_LIBRARY = "libc++"; 322 | CLANG_ENABLE_MODULES = YES; 323 | CLANG_ENABLE_OBJC_ARC = YES; 324 | CLANG_ENABLE_OBJC_WEAK = YES; 325 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_COMMA = YES; 328 | CLANG_WARN_CONSTANT_CONVERSION = YES; 329 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 330 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 331 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 332 | CLANG_WARN_EMPTY_BODY = YES; 333 | CLANG_WARN_ENUM_CONVERSION = YES; 334 | CLANG_WARN_INFINITE_RECURSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 338 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 340 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 341 | CLANG_WARN_STRICT_PROTOTYPES = YES; 342 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 343 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | COPY_PHASE_STRIP = NO; 347 | CURRENT_PROJECT_VERSION = 1; 348 | DEBUG_INFORMATION_FORMAT = dwarf; 349 | ENABLE_STRICT_OBJC_MSGSEND = YES; 350 | ENABLE_TESTABILITY = YES; 351 | GCC_C_LANGUAGE_STANDARD = gnu11; 352 | GCC_DYNAMIC_NO_PIC = NO; 353 | GCC_NO_COMMON_BLOCKS = YES; 354 | GCC_OPTIMIZATION_LEVEL = 0; 355 | GCC_PREPROCESSOR_DEFINITIONS = ( 356 | "DEBUG=1", 357 | "$(inherited)", 358 | ); 359 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 360 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 361 | GCC_WARN_UNDECLARED_SELECTOR = YES; 362 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 363 | GCC_WARN_UNUSED_FUNCTION = YES; 364 | GCC_WARN_UNUSED_VARIABLE = YES; 365 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 366 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 367 | MTL_FAST_MATH = YES; 368 | ONLY_ACTIVE_ARCH = YES; 369 | SDKROOT = iphoneos; 370 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 371 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 372 | VERSIONING_SYSTEM = "apple-generic"; 373 | VERSION_INFO_PREFIX = ""; 374 | }; 375 | name = Debug; 376 | }; 377 | 370DA4C82440D8C300BAB2B7 /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ALWAYS_SEARCH_USER_PATHS = NO; 381 | CLANG_ANALYZER_NONNULL = YES; 382 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 383 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 384 | CLANG_CXX_LIBRARY = "libc++"; 385 | CLANG_ENABLE_MODULES = YES; 386 | CLANG_ENABLE_OBJC_ARC = YES; 387 | CLANG_ENABLE_OBJC_WEAK = YES; 388 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 389 | CLANG_WARN_BOOL_CONVERSION = YES; 390 | CLANG_WARN_COMMA = YES; 391 | CLANG_WARN_CONSTANT_CONVERSION = YES; 392 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 393 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 394 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INFINITE_RECURSION = YES; 398 | CLANG_WARN_INT_CONVERSION = YES; 399 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 400 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 401 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 403 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 404 | CLANG_WARN_STRICT_PROTOTYPES = YES; 405 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 406 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 407 | CLANG_WARN_UNREACHABLE_CODE = YES; 408 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 409 | COPY_PHASE_STRIP = NO; 410 | CURRENT_PROJECT_VERSION = 1; 411 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 412 | ENABLE_NS_ASSERTIONS = NO; 413 | ENABLE_STRICT_OBJC_MSGSEND = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu11; 415 | GCC_NO_COMMON_BLOCKS = YES; 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 423 | MTL_ENABLE_DEBUG_INFO = NO; 424 | MTL_FAST_MATH = YES; 425 | SDKROOT = iphoneos; 426 | SWIFT_COMPILATION_MODE = wholemodule; 427 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 428 | VALIDATE_PRODUCT = YES; 429 | VERSIONING_SYSTEM = "apple-generic"; 430 | VERSION_INFO_PREFIX = ""; 431 | }; 432 | name = Release; 433 | }; 434 | 370DA4CA2440D8C300BAB2B7 /* Debug */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | CLANG_ENABLE_MODULES = YES; 438 | CODE_SIGN_IDENTITY = "Apple Development"; 439 | CODE_SIGN_STYLE = Automatic; 440 | DEFINES_MODULE = YES; 441 | DEVELOPMENT_TEAM = 67VC2B47W7; 442 | DYLIB_COMPATIBILITY_VERSION = 1; 443 | DYLIB_CURRENT_VERSION = 1; 444 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 445 | INFOPLIST_FILE = Wyler/Info.plist; 446 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 447 | LD_RUNPATH_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "@executable_path/Frameworks", 450 | "@loader_path/Frameworks", 451 | ); 452 | PRODUCT_BUNDLE_IDENTIFIER = com.casaseca.Wyler; 453 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 454 | PROVISIONING_PROFILE_SPECIFIER = ""; 455 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 456 | SKIP_INSTALL = YES; 457 | SUPPORTS_MACCATALYST = NO; 458 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Debug; 463 | }; 464 | 370DA4CB2440D8C300BAB2B7 /* Release */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | CLANG_ENABLE_MODULES = YES; 468 | CODE_SIGN_IDENTITY = "Apple Development"; 469 | CODE_SIGN_STYLE = Automatic; 470 | DEFINES_MODULE = YES; 471 | DEVELOPMENT_TEAM = 67VC2B47W7; 472 | DYLIB_COMPATIBILITY_VERSION = 1; 473 | DYLIB_CURRENT_VERSION = 1; 474 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 475 | INFOPLIST_FILE = Wyler/Info.plist; 476 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 477 | LD_RUNPATH_SEARCH_PATHS = ( 478 | "$(inherited)", 479 | "@executable_path/Frameworks", 480 | "@loader_path/Frameworks", 481 | ); 482 | PRODUCT_BUNDLE_IDENTIFIER = com.casaseca.Wyler; 483 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 484 | PROVISIONING_PROFILE_SPECIFIER = ""; 485 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 486 | SKIP_INSTALL = YES; 487 | SUPPORTS_MACCATALYST = NO; 488 | SWIFT_VERSION = 5.0; 489 | TARGETED_DEVICE_FAMILY = "1,2"; 490 | }; 491 | name = Release; 492 | }; 493 | 370DA4E22440D8DE00BAB2B7 /* Debug */ = { 494 | isa = XCBuildConfiguration; 495 | buildSettings = { 496 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 497 | CODE_SIGN_STYLE = Automatic; 498 | DEVELOPMENT_ASSET_PATHS = "\"SampleApp/Preview Content\""; 499 | DEVELOPMENT_TEAM = EKP9ZV782L; 500 | ENABLE_PREVIEWS = YES; 501 | INFOPLIST_FILE = SampleApp/Info.plist; 502 | LD_RUNPATH_SEARCH_PATHS = ( 503 | "$(inherited)", 504 | "@executable_path/Frameworks", 505 | ); 506 | PRODUCT_BUNDLE_IDENTIFIER = de.casaseca.Wyler.SampleApp; 507 | PRODUCT_NAME = "$(TARGET_NAME)"; 508 | SWIFT_VERSION = 5.0; 509 | TARGETED_DEVICE_FAMILY = "1,2"; 510 | }; 511 | name = Debug; 512 | }; 513 | 370DA4E32440D8DE00BAB2B7 /* Release */ = { 514 | isa = XCBuildConfiguration; 515 | buildSettings = { 516 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 517 | CODE_SIGN_STYLE = Automatic; 518 | DEVELOPMENT_ASSET_PATHS = "\"SampleApp/Preview Content\""; 519 | DEVELOPMENT_TEAM = EKP9ZV782L; 520 | ENABLE_PREVIEWS = YES; 521 | INFOPLIST_FILE = SampleApp/Info.plist; 522 | LD_RUNPATH_SEARCH_PATHS = ( 523 | "$(inherited)", 524 | "@executable_path/Frameworks", 525 | ); 526 | PRODUCT_BUNDLE_IDENTIFIER = de.casaseca.Wyler.SampleApp; 527 | PRODUCT_NAME = "$(TARGET_NAME)"; 528 | SWIFT_VERSION = 5.0; 529 | TARGETED_DEVICE_FAMILY = "1,2"; 530 | }; 531 | name = Release; 532 | }; 533 | /* End XCBuildConfiguration section */ 534 | 535 | /* Begin XCConfigurationList section */ 536 | 370DA4BB2440D8C300BAB2B7 /* Build configuration list for PBXProject "Wyler" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | 370DA4C72440D8C300BAB2B7 /* Debug */, 540 | 370DA4C82440D8C300BAB2B7 /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | 370DA4C92440D8C300BAB2B7 /* Build configuration list for PBXNativeTarget "Wyler" */ = { 546 | isa = XCConfigurationList; 547 | buildConfigurations = ( 548 | 370DA4CA2440D8C300BAB2B7 /* Debug */, 549 | 370DA4CB2440D8C300BAB2B7 /* Release */, 550 | ); 551 | defaultConfigurationIsVisible = 0; 552 | defaultConfigurationName = Release; 553 | }; 554 | 370DA4E12440D8DE00BAB2B7 /* Build configuration list for PBXNativeTarget "SampleApp" */ = { 555 | isa = XCConfigurationList; 556 | buildConfigurations = ( 557 | 370DA4E22440D8DE00BAB2B7 /* Debug */, 558 | 370DA4E32440D8DE00BAB2B7 /* Release */, 559 | ); 560 | defaultConfigurationIsVisible = 0; 561 | defaultConfigurationName = Release; 562 | }; 563 | /* End XCConfigurationList section */ 564 | }; 565 | rootObject = 370DA4B82440D8C300BAB2B7 /* Project object */; 566 | } 567 | --------------------------------------------------------------------------------