├── README.md ├── Teleprompter_iOS.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── ileja.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── ileja.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Teleprompter_iOS ├── AdjustSliderView.h ├── AdjustSliderView.m ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── BackgroundTaskManager │ ├── BackgroundTaskManager.h │ ├── BackgroundTaskManager.m │ └── pomodoSound.wav ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── PlayscriptTextView.h ├── PlayscriptTextView.m ├── SceneDelegate.h ├── SceneDelegate.m ├── ViewController.h ├── ViewController.m ├── holder.mp4 └── main.m ├── Teleprompter_iOSTests └── Teleprompter_iOSTests.m └── Teleprompter_iOSUITests ├── Teleprompter_iOSUITests.m └── Teleprompter_iOSUITestsLaunchTests.m /README.md: -------------------------------------------------------------------------------- 1 | # Teleprompter_iOS 2 | iOS端提词器,画中画功能 3 | 4 | ## 前言 5 | 最近在做直播机业务,手机端iOS端需做提词器功能,也需要用到画中画功能。功能描述简单,在找相关资料的时候发现能给到的帮助很少,其实关键的地方很少很少,但以此谋利的比较多,本着程序员的开源精神,现在分享出来。 6 | 7 | **同道自取、异途绕行。** 8 | 9 | ## 问题阐述 10 | 其中的几个问题点先总结一下: 11 | ``` 12 | 1、展示在画中画页面上 13 | 14 | 2、双击画中画,大小切换导致变形 15 | 16 | 3、画中画页面隐藏播放、快进、快退按钮 17 | 18 | 4、切后台、打开照相机视频等保活问题 19 | ``` 20 | 下面以代码形式讲解问题的解决方案 21 | 22 | ## 提词展示在画中画 23 | 通过获取Window,拿到画中画所在的PGHostedWindow,合适时机将提词器放在PGHostedWindow上即可 24 | ``` 25 | firstWindow = [UIApplication sharedApplication].windows.firstObject; 26 | 27 | [firstWindow addSubview:self.pipTextView]; 28 | ``` 29 | 这一步没有什么难的,需要加到画中画上的页面可以自定义内容 30 | 31 | ## 大小切换导致变形 32 | 开启画中画后,双击可改变画中画的大小,我在代码里为了省事用了frame布局,此时就会在切换大小的时候变形。而正常开发的时候用autolayout布局不会出现此情况。 33 | 我在这里加了重新刷新frame的方法修正这个问题,正常开发autolayout布局可忽略这个问题。 34 | 35 | ## 隐藏无关按钮 36 | 这个问题是大多数同胞卡的问题点,隐藏快进、快退按钮可以通过属性requiresLinearPlayback设置为YES解决,但是仍解决不了播放按钮的隐藏以及打开照相机黑屏问题。 37 | 做如下设置即可 38 | ``` 39 | [self.pipVC setValue:@1 forKey:@"controlsStyle"]; 40 | ``` 41 | 此时也无需设置requiresLinearPlayback属性,全篇关键代码仅此一句。 42 | 43 | ## 切后台保活 44 | 此问题我参考了别人总结的[现成工具](https://blog.csdn.net/WangQingLei0307/article/details/112002904),在SceneDelegate对应方法里直接调用,可参考Demo。 45 | 46 | ## 结束语 47 | 感谢您的star,大家的支持是我不断努力的坚强后盾。 48 | -------------------------------------------------------------------------------- /Teleprompter_iOS.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FD6A23BD28699CA30047140B /* BackgroundTaskManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FD6A23BA28699CA30047140B /* BackgroundTaskManager.m */; }; 11 | FD6A23BE28699CA30047140B /* pomodoSound.wav in Resources */ = {isa = PBXBuildFile; fileRef = FD6A23BC28699CA30047140B /* pomodoSound.wav */; }; 12 | FD86C3AE27C8759D007C5EC0 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C3AD27C8759D007C5EC0 /* AppDelegate.m */; }; 13 | FD86C3B127C8759D007C5EC0 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C3B027C8759D007C5EC0 /* SceneDelegate.m */; }; 14 | FD86C3B427C8759D007C5EC0 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C3B327C8759D007C5EC0 /* ViewController.m */; }; 15 | FD86C3B727C8759D007C5EC0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FD86C3B527C8759D007C5EC0 /* Main.storyboard */; }; 16 | FD86C3B927C8759E007C5EC0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FD86C3B827C8759E007C5EC0 /* Assets.xcassets */; }; 17 | FD86C3BC27C8759E007C5EC0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FD86C3BA27C8759E007C5EC0 /* LaunchScreen.storyboard */; }; 18 | FD86C3BF27C8759E007C5EC0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C3BE27C8759E007C5EC0 /* main.m */; }; 19 | FD86C3C927C8759E007C5EC0 /* Teleprompter_iOSTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C3C827C8759E007C5EC0 /* Teleprompter_iOSTests.m */; }; 20 | FD86C3D327C8759F007C5EC0 /* Teleprompter_iOSUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C3D227C8759F007C5EC0 /* Teleprompter_iOSUITests.m */; }; 21 | FD86C3D527C8759F007C5EC0 /* Teleprompter_iOSUITestsLaunchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C3D427C8759F007C5EC0 /* Teleprompter_iOSUITestsLaunchTests.m */; }; 22 | FD86C3E327C8CA55007C5EC0 /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD86C3E227C8CA55007C5EC0 /* AVKit.framework */; }; 23 | FD86C4B727CCC206007C5EC0 /* PlayscriptTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C4B627CCC206007C5EC0 /* PlayscriptTextView.m */; }; 24 | FD86C4BA27CCC922007C5EC0 /* AdjustSliderView.m in Sources */ = {isa = PBXBuildFile; fileRef = FD86C4B927CCC922007C5EC0 /* AdjustSliderView.m */; }; 25 | FD86C4BC27CE1644007C5EC0 /* holder.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = FD86C4BB27CE1644007C5EC0 /* holder.mp4 */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | FD86C3C527C8759E007C5EC0 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = FD86C3A127C8759D007C5EC0 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = FD86C3A827C8759D007C5EC0; 34 | remoteInfo = Teleprompter_iOS; 35 | }; 36 | FD86C3CF27C8759F007C5EC0 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = FD86C3A127C8759D007C5EC0 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = FD86C3A827C8759D007C5EC0; 41 | remoteInfo = Teleprompter_iOS; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | FD6A23BA28699CA30047140B /* BackgroundTaskManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BackgroundTaskManager.m; sourceTree = ""; }; 47 | FD6A23BB28699CA30047140B /* BackgroundTaskManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BackgroundTaskManager.h; sourceTree = ""; }; 48 | FD6A23BC28699CA30047140B /* pomodoSound.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = pomodoSound.wav; sourceTree = ""; }; 49 | FD86C3A927C8759D007C5EC0 /* Teleprompter_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Teleprompter_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | FD86C3AC27C8759D007C5EC0 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 51 | FD86C3AD27C8759D007C5EC0 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 52 | FD86C3AF27C8759D007C5EC0 /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; 53 | FD86C3B027C8759D007C5EC0 /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; 54 | FD86C3B227C8759D007C5EC0 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 55 | FD86C3B327C8759D007C5EC0 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 56 | FD86C3B627C8759D007C5EC0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | FD86C3B827C8759E007C5EC0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 58 | FD86C3BB27C8759E007C5EC0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 59 | FD86C3BD27C8759E007C5EC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | FD86C3BE27C8759E007C5EC0 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 61 | FD86C3C427C8759E007C5EC0 /* Teleprompter_iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Teleprompter_iOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | FD86C3C827C8759E007C5EC0 /* Teleprompter_iOSTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Teleprompter_iOSTests.m; sourceTree = ""; }; 63 | FD86C3CE27C8759E007C5EC0 /* Teleprompter_iOSUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Teleprompter_iOSUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | FD86C3D227C8759F007C5EC0 /* Teleprompter_iOSUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Teleprompter_iOSUITests.m; sourceTree = ""; }; 65 | FD86C3D427C8759F007C5EC0 /* Teleprompter_iOSUITestsLaunchTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Teleprompter_iOSUITestsLaunchTests.m; sourceTree = ""; }; 66 | FD86C3E227C8CA55007C5EC0 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; 67 | FD86C4B527CCC206007C5EC0 /* PlayscriptTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlayscriptTextView.h; sourceTree = ""; }; 68 | FD86C4B627CCC206007C5EC0 /* PlayscriptTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlayscriptTextView.m; sourceTree = ""; }; 69 | FD86C4B827CCC922007C5EC0 /* AdjustSliderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AdjustSliderView.h; sourceTree = ""; }; 70 | FD86C4B927CCC922007C5EC0 /* AdjustSliderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AdjustSliderView.m; sourceTree = ""; }; 71 | FD86C4BB27CE1644007C5EC0 /* holder.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = holder.mp4; sourceTree = ""; }; 72 | /* End PBXFileReference section */ 73 | 74 | /* Begin PBXFrameworksBuildPhase section */ 75 | FD86C3A627C8759D007C5EC0 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | FD86C3E327C8CA55007C5EC0 /* AVKit.framework in Frameworks */, 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | FD86C3C127C8759E007C5EC0 /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | FD86C3CB27C8759E007C5EC0 /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | FD6A23B928699CA30047140B /* BackgroundTaskManager */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | FD6A23BB28699CA30047140B /* BackgroundTaskManager.h */, 104 | FD6A23BA28699CA30047140B /* BackgroundTaskManager.m */, 105 | FD6A23BC28699CA30047140B /* pomodoSound.wav */, 106 | ); 107 | path = BackgroundTaskManager; 108 | sourceTree = ""; 109 | }; 110 | FD86C3A027C8759D007C5EC0 = { 111 | isa = PBXGroup; 112 | children = ( 113 | FD86C3AB27C8759D007C5EC0 /* Teleprompter_iOS */, 114 | FD86C3C727C8759E007C5EC0 /* Teleprompter_iOSTests */, 115 | FD86C3D127C8759F007C5EC0 /* Teleprompter_iOSUITests */, 116 | FD86C3AA27C8759D007C5EC0 /* Products */, 117 | FD86C3E127C8CA55007C5EC0 /* Frameworks */, 118 | ); 119 | sourceTree = ""; 120 | }; 121 | FD86C3AA27C8759D007C5EC0 /* Products */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | FD86C3A927C8759D007C5EC0 /* Teleprompter_iOS.app */, 125 | FD86C3C427C8759E007C5EC0 /* Teleprompter_iOSTests.xctest */, 126 | FD86C3CE27C8759E007C5EC0 /* Teleprompter_iOSUITests.xctest */, 127 | ); 128 | name = Products; 129 | sourceTree = ""; 130 | }; 131 | FD86C3AB27C8759D007C5EC0 /* Teleprompter_iOS */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | FD86C3AC27C8759D007C5EC0 /* AppDelegate.h */, 135 | FD86C3AD27C8759D007C5EC0 /* AppDelegate.m */, 136 | FD86C3AF27C8759D007C5EC0 /* SceneDelegate.h */, 137 | FD86C3B027C8759D007C5EC0 /* SceneDelegate.m */, 138 | FD86C3B227C8759D007C5EC0 /* ViewController.h */, 139 | FD86C3B327C8759D007C5EC0 /* ViewController.m */, 140 | FD86C4B527CCC206007C5EC0 /* PlayscriptTextView.h */, 141 | FD86C4B627CCC206007C5EC0 /* PlayscriptTextView.m */, 142 | FD86C4B827CCC922007C5EC0 /* AdjustSliderView.h */, 143 | FD86C4B927CCC922007C5EC0 /* AdjustSliderView.m */, 144 | FD6A23B928699CA30047140B /* BackgroundTaskManager */, 145 | FD86C4BB27CE1644007C5EC0 /* holder.mp4 */, 146 | FD86C3B527C8759D007C5EC0 /* Main.storyboard */, 147 | FD86C3B827C8759E007C5EC0 /* Assets.xcassets */, 148 | FD86C3BA27C8759E007C5EC0 /* LaunchScreen.storyboard */, 149 | FD86C3BD27C8759E007C5EC0 /* Info.plist */, 150 | FD86C3BE27C8759E007C5EC0 /* main.m */, 151 | ); 152 | path = Teleprompter_iOS; 153 | sourceTree = ""; 154 | }; 155 | FD86C3C727C8759E007C5EC0 /* Teleprompter_iOSTests */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | FD86C3C827C8759E007C5EC0 /* Teleprompter_iOSTests.m */, 159 | ); 160 | path = Teleprompter_iOSTests; 161 | sourceTree = ""; 162 | }; 163 | FD86C3D127C8759F007C5EC0 /* Teleprompter_iOSUITests */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | FD86C3D227C8759F007C5EC0 /* Teleprompter_iOSUITests.m */, 167 | FD86C3D427C8759F007C5EC0 /* Teleprompter_iOSUITestsLaunchTests.m */, 168 | ); 169 | path = Teleprompter_iOSUITests; 170 | sourceTree = ""; 171 | }; 172 | FD86C3E127C8CA55007C5EC0 /* Frameworks */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | FD86C3E227C8CA55007C5EC0 /* AVKit.framework */, 176 | ); 177 | name = Frameworks; 178 | sourceTree = ""; 179 | }; 180 | /* End PBXGroup section */ 181 | 182 | /* Begin PBXNativeTarget section */ 183 | FD86C3A827C8759D007C5EC0 /* Teleprompter_iOS */ = { 184 | isa = PBXNativeTarget; 185 | buildConfigurationList = FD86C3D827C8759F007C5EC0 /* Build configuration list for PBXNativeTarget "Teleprompter_iOS" */; 186 | buildPhases = ( 187 | FD86C3A527C8759D007C5EC0 /* Sources */, 188 | FD86C3A627C8759D007C5EC0 /* Frameworks */, 189 | FD86C3A727C8759D007C5EC0 /* Resources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | ); 195 | name = Teleprompter_iOS; 196 | productName = Teleprompter_iOS; 197 | productReference = FD86C3A927C8759D007C5EC0 /* Teleprompter_iOS.app */; 198 | productType = "com.apple.product-type.application"; 199 | }; 200 | FD86C3C327C8759E007C5EC0 /* Teleprompter_iOSTests */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = FD86C3DB27C8759F007C5EC0 /* Build configuration list for PBXNativeTarget "Teleprompter_iOSTests" */; 203 | buildPhases = ( 204 | FD86C3C027C8759E007C5EC0 /* Sources */, 205 | FD86C3C127C8759E007C5EC0 /* Frameworks */, 206 | FD86C3C227C8759E007C5EC0 /* Resources */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | FD86C3C627C8759E007C5EC0 /* PBXTargetDependency */, 212 | ); 213 | name = Teleprompter_iOSTests; 214 | productName = Teleprompter_iOSTests; 215 | productReference = FD86C3C427C8759E007C5EC0 /* Teleprompter_iOSTests.xctest */; 216 | productType = "com.apple.product-type.bundle.unit-test"; 217 | }; 218 | FD86C3CD27C8759E007C5EC0 /* Teleprompter_iOSUITests */ = { 219 | isa = PBXNativeTarget; 220 | buildConfigurationList = FD86C3DE27C8759F007C5EC0 /* Build configuration list for PBXNativeTarget "Teleprompter_iOSUITests" */; 221 | buildPhases = ( 222 | FD86C3CA27C8759E007C5EC0 /* Sources */, 223 | FD86C3CB27C8759E007C5EC0 /* Frameworks */, 224 | FD86C3CC27C8759E007C5EC0 /* Resources */, 225 | ); 226 | buildRules = ( 227 | ); 228 | dependencies = ( 229 | FD86C3D027C8759F007C5EC0 /* PBXTargetDependency */, 230 | ); 231 | name = Teleprompter_iOSUITests; 232 | productName = Teleprompter_iOSUITests; 233 | productReference = FD86C3CE27C8759E007C5EC0 /* Teleprompter_iOSUITests.xctest */; 234 | productType = "com.apple.product-type.bundle.ui-testing"; 235 | }; 236 | /* End PBXNativeTarget section */ 237 | 238 | /* Begin PBXProject section */ 239 | FD86C3A127C8759D007C5EC0 /* Project object */ = { 240 | isa = PBXProject; 241 | attributes = { 242 | BuildIndependentTargetsInParallel = 1; 243 | LastUpgradeCheck = 1320; 244 | TargetAttributes = { 245 | FD86C3A827C8759D007C5EC0 = { 246 | CreatedOnToolsVersion = 13.2.1; 247 | }; 248 | FD86C3C327C8759E007C5EC0 = { 249 | CreatedOnToolsVersion = 13.2.1; 250 | TestTargetID = FD86C3A827C8759D007C5EC0; 251 | }; 252 | FD86C3CD27C8759E007C5EC0 = { 253 | CreatedOnToolsVersion = 13.2.1; 254 | TestTargetID = FD86C3A827C8759D007C5EC0; 255 | }; 256 | }; 257 | }; 258 | buildConfigurationList = FD86C3A427C8759D007C5EC0 /* Build configuration list for PBXProject "Teleprompter_iOS" */; 259 | compatibilityVersion = "Xcode 13.0"; 260 | developmentRegion = en; 261 | hasScannedForEncodings = 0; 262 | knownRegions = ( 263 | en, 264 | Base, 265 | ); 266 | mainGroup = FD86C3A027C8759D007C5EC0; 267 | productRefGroup = FD86C3AA27C8759D007C5EC0 /* Products */; 268 | projectDirPath = ""; 269 | projectRoot = ""; 270 | targets = ( 271 | FD86C3A827C8759D007C5EC0 /* Teleprompter_iOS */, 272 | FD86C3C327C8759E007C5EC0 /* Teleprompter_iOSTests */, 273 | FD86C3CD27C8759E007C5EC0 /* Teleprompter_iOSUITests */, 274 | ); 275 | }; 276 | /* End PBXProject section */ 277 | 278 | /* Begin PBXResourcesBuildPhase section */ 279 | FD86C3A727C8759D007C5EC0 /* Resources */ = { 280 | isa = PBXResourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | FD86C3BC27C8759E007C5EC0 /* LaunchScreen.storyboard in Resources */, 284 | FD86C3B927C8759E007C5EC0 /* Assets.xcassets in Resources */, 285 | FD86C4BC27CE1644007C5EC0 /* holder.mp4 in Resources */, 286 | FD6A23BE28699CA30047140B /* pomodoSound.wav in Resources */, 287 | FD86C3B727C8759D007C5EC0 /* Main.storyboard in Resources */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | FD86C3C227C8759E007C5EC0 /* Resources */ = { 292 | isa = PBXResourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | FD86C3CC27C8759E007C5EC0 /* Resources */ = { 299 | isa = PBXResourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | /* End PBXResourcesBuildPhase section */ 306 | 307 | /* Begin PBXSourcesBuildPhase section */ 308 | FD86C3A527C8759D007C5EC0 /* Sources */ = { 309 | isa = PBXSourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | FD86C3B427C8759D007C5EC0 /* ViewController.m in Sources */, 313 | FD86C4B727CCC206007C5EC0 /* PlayscriptTextView.m in Sources */, 314 | FD86C3AE27C8759D007C5EC0 /* AppDelegate.m in Sources */, 315 | FD86C3BF27C8759E007C5EC0 /* main.m in Sources */, 316 | FD6A23BD28699CA30047140B /* BackgroundTaskManager.m in Sources */, 317 | FD86C4BA27CCC922007C5EC0 /* AdjustSliderView.m in Sources */, 318 | FD86C3B127C8759D007C5EC0 /* SceneDelegate.m in Sources */, 319 | ); 320 | runOnlyForDeploymentPostprocessing = 0; 321 | }; 322 | FD86C3C027C8759E007C5EC0 /* Sources */ = { 323 | isa = PBXSourcesBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | FD86C3C927C8759E007C5EC0 /* Teleprompter_iOSTests.m in Sources */, 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | FD86C3CA27C8759E007C5EC0 /* Sources */ = { 331 | isa = PBXSourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | FD86C3D327C8759F007C5EC0 /* Teleprompter_iOSUITests.m in Sources */, 335 | FD86C3D527C8759F007C5EC0 /* Teleprompter_iOSUITestsLaunchTests.m in Sources */, 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | /* End PBXSourcesBuildPhase section */ 340 | 341 | /* Begin PBXTargetDependency section */ 342 | FD86C3C627C8759E007C5EC0 /* PBXTargetDependency */ = { 343 | isa = PBXTargetDependency; 344 | target = FD86C3A827C8759D007C5EC0 /* Teleprompter_iOS */; 345 | targetProxy = FD86C3C527C8759E007C5EC0 /* PBXContainerItemProxy */; 346 | }; 347 | FD86C3D027C8759F007C5EC0 /* PBXTargetDependency */ = { 348 | isa = PBXTargetDependency; 349 | target = FD86C3A827C8759D007C5EC0 /* Teleprompter_iOS */; 350 | targetProxy = FD86C3CF27C8759F007C5EC0 /* PBXContainerItemProxy */; 351 | }; 352 | /* End PBXTargetDependency section */ 353 | 354 | /* Begin PBXVariantGroup section */ 355 | FD86C3B527C8759D007C5EC0 /* Main.storyboard */ = { 356 | isa = PBXVariantGroup; 357 | children = ( 358 | FD86C3B627C8759D007C5EC0 /* Base */, 359 | ); 360 | name = Main.storyboard; 361 | sourceTree = ""; 362 | }; 363 | FD86C3BA27C8759E007C5EC0 /* LaunchScreen.storyboard */ = { 364 | isa = PBXVariantGroup; 365 | children = ( 366 | FD86C3BB27C8759E007C5EC0 /* Base */, 367 | ); 368 | name = LaunchScreen.storyboard; 369 | sourceTree = ""; 370 | }; 371 | /* End PBXVariantGroup section */ 372 | 373 | /* Begin XCBuildConfiguration section */ 374 | FD86C3D627C8759F007C5EC0 /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ALWAYS_SEARCH_USER_PATHS = NO; 378 | CLANG_ANALYZER_NONNULL = YES; 379 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 380 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 381 | CLANG_CXX_LIBRARY = "libc++"; 382 | CLANG_ENABLE_MODULES = YES; 383 | CLANG_ENABLE_OBJC_ARC = YES; 384 | CLANG_ENABLE_OBJC_WEAK = YES; 385 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 386 | CLANG_WARN_BOOL_CONVERSION = YES; 387 | CLANG_WARN_COMMA = YES; 388 | CLANG_WARN_CONSTANT_CONVERSION = YES; 389 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 392 | CLANG_WARN_EMPTY_BODY = YES; 393 | CLANG_WARN_ENUM_CONVERSION = YES; 394 | CLANG_WARN_INFINITE_RECURSION = YES; 395 | CLANG_WARN_INT_CONVERSION = YES; 396 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 398 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 399 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 400 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 401 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 402 | CLANG_WARN_STRICT_PROTOTYPES = YES; 403 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 404 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 405 | CLANG_WARN_UNREACHABLE_CODE = YES; 406 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 407 | COPY_PHASE_STRIP = NO; 408 | DEBUG_INFORMATION_FORMAT = dwarf; 409 | ENABLE_STRICT_OBJC_MSGSEND = YES; 410 | ENABLE_TESTABILITY = YES; 411 | GCC_C_LANGUAGE_STANDARD = gnu11; 412 | GCC_DYNAMIC_NO_PIC = NO; 413 | GCC_NO_COMMON_BLOCKS = YES; 414 | GCC_OPTIMIZATION_LEVEL = 0; 415 | GCC_PREPROCESSOR_DEFINITIONS = ( 416 | "DEBUG=1", 417 | "$(inherited)", 418 | ); 419 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 420 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 421 | GCC_WARN_UNDECLARED_SELECTOR = YES; 422 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 423 | GCC_WARN_UNUSED_FUNCTION = YES; 424 | GCC_WARN_UNUSED_VARIABLE = YES; 425 | IPHONEOS_DEPLOYMENT_TARGET = 15.2; 426 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 427 | MTL_FAST_MATH = YES; 428 | ONLY_ACTIVE_ARCH = YES; 429 | SDKROOT = iphoneos; 430 | }; 431 | name = Debug; 432 | }; 433 | FD86C3D727C8759F007C5EC0 /* Release */ = { 434 | isa = XCBuildConfiguration; 435 | buildSettings = { 436 | ALWAYS_SEARCH_USER_PATHS = NO; 437 | CLANG_ANALYZER_NONNULL = YES; 438 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 439 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 440 | CLANG_CXX_LIBRARY = "libc++"; 441 | CLANG_ENABLE_MODULES = YES; 442 | CLANG_ENABLE_OBJC_ARC = YES; 443 | CLANG_ENABLE_OBJC_WEAK = YES; 444 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 445 | CLANG_WARN_BOOL_CONVERSION = YES; 446 | CLANG_WARN_COMMA = YES; 447 | CLANG_WARN_CONSTANT_CONVERSION = YES; 448 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 449 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 450 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 451 | CLANG_WARN_EMPTY_BODY = YES; 452 | CLANG_WARN_ENUM_CONVERSION = YES; 453 | CLANG_WARN_INFINITE_RECURSION = YES; 454 | CLANG_WARN_INT_CONVERSION = YES; 455 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 456 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 457 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 458 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 459 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 460 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 461 | CLANG_WARN_STRICT_PROTOTYPES = YES; 462 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 463 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 464 | CLANG_WARN_UNREACHABLE_CODE = YES; 465 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 466 | COPY_PHASE_STRIP = NO; 467 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 468 | ENABLE_NS_ASSERTIONS = NO; 469 | ENABLE_STRICT_OBJC_MSGSEND = YES; 470 | GCC_C_LANGUAGE_STANDARD = gnu11; 471 | GCC_NO_COMMON_BLOCKS = YES; 472 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 473 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 474 | GCC_WARN_UNDECLARED_SELECTOR = YES; 475 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 476 | GCC_WARN_UNUSED_FUNCTION = YES; 477 | GCC_WARN_UNUSED_VARIABLE = YES; 478 | IPHONEOS_DEPLOYMENT_TARGET = 15.2; 479 | MTL_ENABLE_DEBUG_INFO = NO; 480 | MTL_FAST_MATH = YES; 481 | SDKROOT = iphoneos; 482 | VALIDATE_PRODUCT = YES; 483 | }; 484 | name = Release; 485 | }; 486 | FD86C3D927C8759F007C5EC0 /* Debug */ = { 487 | isa = XCBuildConfiguration; 488 | buildSettings = { 489 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 490 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 491 | CODE_SIGN_STYLE = Automatic; 492 | CURRENT_PROJECT_VERSION = 1; 493 | DEVELOPMENT_TEAM = 86KBC6T99Q; 494 | GENERATE_INFOPLIST_FILE = YES; 495 | INFOPLIST_FILE = Teleprompter_iOS/Info.plist; 496 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 497 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 498 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 499 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 500 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 501 | INFOPLIST_KEY_UIUserInterfaceStyle = Light; 502 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 503 | LD_RUNPATH_SEARCH_PATHS = ( 504 | "$(inherited)", 505 | "@executable_path/Frameworks", 506 | ); 507 | MARKETING_VERSION = 1.0; 508 | PRODUCT_BUNDLE_IDENTIFIER = "fz.Teleprompter-iOS"; 509 | PRODUCT_NAME = "$(TARGET_NAME)"; 510 | SWIFT_EMIT_LOC_STRINGS = YES; 511 | TARGETED_DEVICE_FAMILY = 1; 512 | }; 513 | name = Debug; 514 | }; 515 | FD86C3DA27C8759F007C5EC0 /* Release */ = { 516 | isa = XCBuildConfiguration; 517 | buildSettings = { 518 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 519 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 520 | CODE_SIGN_STYLE = Automatic; 521 | CURRENT_PROJECT_VERSION = 1; 522 | DEVELOPMENT_TEAM = 86KBC6T99Q; 523 | GENERATE_INFOPLIST_FILE = YES; 524 | INFOPLIST_FILE = Teleprompter_iOS/Info.plist; 525 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 526 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 527 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 528 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 529 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 530 | INFOPLIST_KEY_UIUserInterfaceStyle = Light; 531 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 532 | LD_RUNPATH_SEARCH_PATHS = ( 533 | "$(inherited)", 534 | "@executable_path/Frameworks", 535 | ); 536 | MARKETING_VERSION = 1.0; 537 | PRODUCT_BUNDLE_IDENTIFIER = "fz.Teleprompter-iOS"; 538 | PRODUCT_NAME = "$(TARGET_NAME)"; 539 | SWIFT_EMIT_LOC_STRINGS = YES; 540 | TARGETED_DEVICE_FAMILY = 1; 541 | }; 542 | name = Release; 543 | }; 544 | FD86C3DC27C8759F007C5EC0 /* Debug */ = { 545 | isa = XCBuildConfiguration; 546 | buildSettings = { 547 | BUNDLE_LOADER = "$(TEST_HOST)"; 548 | CODE_SIGN_STYLE = Automatic; 549 | CURRENT_PROJECT_VERSION = 1; 550 | DEVELOPMENT_TEAM = 86KBC6T99Q; 551 | GENERATE_INFOPLIST_FILE = YES; 552 | IPHONEOS_DEPLOYMENT_TARGET = 15.2; 553 | MARKETING_VERSION = 1.0; 554 | PRODUCT_BUNDLE_IDENTIFIER = "fz.Teleprompter-iOSTests"; 555 | PRODUCT_NAME = "$(TARGET_NAME)"; 556 | SWIFT_EMIT_LOC_STRINGS = NO; 557 | TARGETED_DEVICE_FAMILY = "1,2"; 558 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Teleprompter_iOS.app/Teleprompter_iOS"; 559 | }; 560 | name = Debug; 561 | }; 562 | FD86C3DD27C8759F007C5EC0 /* Release */ = { 563 | isa = XCBuildConfiguration; 564 | buildSettings = { 565 | BUNDLE_LOADER = "$(TEST_HOST)"; 566 | CODE_SIGN_STYLE = Automatic; 567 | CURRENT_PROJECT_VERSION = 1; 568 | DEVELOPMENT_TEAM = 86KBC6T99Q; 569 | GENERATE_INFOPLIST_FILE = YES; 570 | IPHONEOS_DEPLOYMENT_TARGET = 15.2; 571 | MARKETING_VERSION = 1.0; 572 | PRODUCT_BUNDLE_IDENTIFIER = "fz.Teleprompter-iOSTests"; 573 | PRODUCT_NAME = "$(TARGET_NAME)"; 574 | SWIFT_EMIT_LOC_STRINGS = NO; 575 | TARGETED_DEVICE_FAMILY = "1,2"; 576 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Teleprompter_iOS.app/Teleprompter_iOS"; 577 | }; 578 | name = Release; 579 | }; 580 | FD86C3DF27C8759F007C5EC0 /* Debug */ = { 581 | isa = XCBuildConfiguration; 582 | buildSettings = { 583 | CODE_SIGN_STYLE = Automatic; 584 | CURRENT_PROJECT_VERSION = 1; 585 | DEVELOPMENT_TEAM = 86KBC6T99Q; 586 | GENERATE_INFOPLIST_FILE = YES; 587 | MARKETING_VERSION = 1.0; 588 | PRODUCT_BUNDLE_IDENTIFIER = "fz.Teleprompter-iOSUITests"; 589 | PRODUCT_NAME = "$(TARGET_NAME)"; 590 | SWIFT_EMIT_LOC_STRINGS = NO; 591 | TARGETED_DEVICE_FAMILY = "1,2"; 592 | TEST_TARGET_NAME = Teleprompter_iOS; 593 | }; 594 | name = Debug; 595 | }; 596 | FD86C3E027C8759F007C5EC0 /* Release */ = { 597 | isa = XCBuildConfiguration; 598 | buildSettings = { 599 | CODE_SIGN_STYLE = Automatic; 600 | CURRENT_PROJECT_VERSION = 1; 601 | DEVELOPMENT_TEAM = 86KBC6T99Q; 602 | GENERATE_INFOPLIST_FILE = YES; 603 | MARKETING_VERSION = 1.0; 604 | PRODUCT_BUNDLE_IDENTIFIER = "fz.Teleprompter-iOSUITests"; 605 | PRODUCT_NAME = "$(TARGET_NAME)"; 606 | SWIFT_EMIT_LOC_STRINGS = NO; 607 | TARGETED_DEVICE_FAMILY = "1,2"; 608 | TEST_TARGET_NAME = Teleprompter_iOS; 609 | }; 610 | name = Release; 611 | }; 612 | /* End XCBuildConfiguration section */ 613 | 614 | /* Begin XCConfigurationList section */ 615 | FD86C3A427C8759D007C5EC0 /* Build configuration list for PBXProject "Teleprompter_iOS" */ = { 616 | isa = XCConfigurationList; 617 | buildConfigurations = ( 618 | FD86C3D627C8759F007C5EC0 /* Debug */, 619 | FD86C3D727C8759F007C5EC0 /* Release */, 620 | ); 621 | defaultConfigurationIsVisible = 0; 622 | defaultConfigurationName = Release; 623 | }; 624 | FD86C3D827C8759F007C5EC0 /* Build configuration list for PBXNativeTarget "Teleprompter_iOS" */ = { 625 | isa = XCConfigurationList; 626 | buildConfigurations = ( 627 | FD86C3D927C8759F007C5EC0 /* Debug */, 628 | FD86C3DA27C8759F007C5EC0 /* Release */, 629 | ); 630 | defaultConfigurationIsVisible = 0; 631 | defaultConfigurationName = Release; 632 | }; 633 | FD86C3DB27C8759F007C5EC0 /* Build configuration list for PBXNativeTarget "Teleprompter_iOSTests" */ = { 634 | isa = XCConfigurationList; 635 | buildConfigurations = ( 636 | FD86C3DC27C8759F007C5EC0 /* Debug */, 637 | FD86C3DD27C8759F007C5EC0 /* Release */, 638 | ); 639 | defaultConfigurationIsVisible = 0; 640 | defaultConfigurationName = Release; 641 | }; 642 | FD86C3DE27C8759F007C5EC0 /* Build configuration list for PBXNativeTarget "Teleprompter_iOSUITests" */ = { 643 | isa = XCConfigurationList; 644 | buildConfigurations = ( 645 | FD86C3DF27C8759F007C5EC0 /* Debug */, 646 | FD86C3E027C8759F007C5EC0 /* Release */, 647 | ); 648 | defaultConfigurationIsVisible = 0; 649 | defaultConfigurationName = Release; 650 | }; 651 | /* End XCConfigurationList section */ 652 | }; 653 | rootObject = FD86C3A127C8759D007C5EC0 /* Project object */; 654 | } 655 | -------------------------------------------------------------------------------- /Teleprompter_iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Teleprompter_iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Teleprompter_iOS.xcodeproj/project.xcworkspace/xcuserdata/ileja.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuzheng0301/Teleprompter_iOS/b70fdd3fc263a5a19846f22f0620cbd3035339c5/Teleprompter_iOS.xcodeproj/project.xcworkspace/xcuserdata/ileja.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Teleprompter_iOS.xcodeproj/xcuserdata/ileja.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Teleprompter_iOS.xcodeproj/xcuserdata/ileja.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Teleprompter_iOS.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Teleprompter_iOS/AdjustSliderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // AdjustSliderView.h 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/28. 6 | // 7 | // 调整slider控件 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AdjustSliderView : UIView 14 | 15 | /// 说明 16 | @property (nonatomic, strong) NSString *title; 17 | 18 | /// 指定进度 19 | @property (nonatomic, assign) float sliderValue; 20 | 21 | /// 进度最小值 22 | @property(nonatomic, assign) float minimumValue; 23 | /// 进度最大值 24 | @property(nonatomic, assign) float maximumValue; 25 | 26 | @property (nonatomic, strong) UISlider *stempSlider; 27 | 28 | /// slider事件 29 | /// @param target 控制器 30 | /// @param action 事件 31 | /// @param controlEvents 状态 32 | - (void)sliderAddTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents; 33 | 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /Teleprompter_iOS/AdjustSliderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // AdjustSliderView.m 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/28. 6 | // 7 | 8 | #import "AdjustSliderView.h" 9 | 10 | @interface AdjustSliderView () 11 | 12 | @property (nonatomic, strong) UILabel *titleLab; 13 | @property (nonatomic, strong) UILabel *stempLab; 14 | 15 | @end 16 | 17 | @implementation AdjustSliderView 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame 20 | { 21 | self = [super initWithFrame:frame]; 22 | if (self) { 23 | 24 | [self createUIWithFrame:frame]; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)createUIWithFrame:(CGRect)frame 30 | { 31 | [self addSubview:self.titleLab]; 32 | 33 | [self addSubview:self.stempSlider]; 34 | 35 | [self addSubview:self.stempLab]; 36 | } 37 | 38 | /// 说明 39 | - (void)setTitle:(NSString *)title 40 | { 41 | _title = title; 42 | 43 | self.titleLab.text = title; 44 | } 45 | 46 | /// 进度最小值 47 | - (void)setMinimumValue:(float)minimumValue 48 | { 49 | _minimumValue = minimumValue; 50 | 51 | self.stempSlider.minimumValue = minimumValue; 52 | } 53 | /// 进度最大值 54 | - (void)setMaximumValue:(float)maximumValue 55 | { 56 | _maximumValue = maximumValue; 57 | 58 | self.stempSlider.maximumValue = maximumValue; 59 | } 60 | /// 指定进度 61 | - (void)setSliderValue:(float)sliderValue 62 | { 63 | _sliderValue = sliderValue; 64 | 65 | self.stempSlider.value = sliderValue; 66 | self.stempLab.text = [NSString stringWithFormat:@"%.f",sliderValue]; 67 | } 68 | 69 | /// slider事件 70 | /// @param target 控制器 71 | /// @param action 事件 72 | /// @param controlEvents 状态 73 | - (void)sliderAddTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents 74 | { 75 | [self.stempSlider addTarget:target action:action forControlEvents:controlEvents]; 76 | } 77 | 78 | #pragma mark --- lazyload 79 | - (UILabel *)titleLab 80 | { 81 | if (!_titleLab) { 82 | _titleLab = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 40, self.bounds.size.height)]; 83 | _titleLab.textColor = [UIColor lightGrayColor]; 84 | _titleLab.textAlignment = NSTextAlignmentCenter; 85 | } 86 | return _titleLab; 87 | } 88 | - (UISlider *)stempSlider 89 | { 90 | if (!_stempSlider) { 91 | _stempSlider = [[UISlider alloc]initWithFrame:CGRectMake(60, 0, self.bounds.size.width-120, self.bounds.size.height)]; 92 | } 93 | return _stempSlider; 94 | } 95 | - (UILabel *)stempLab 96 | { 97 | if (!_stempLab) { 98 | _stempLab = [[UILabel alloc]initWithFrame:CGRectMake(self.bounds.size.width-50, 0, 40, self.bounds.size.height)]; 99 | _stempLab.textColor = [UIColor lightGrayColor]; 100 | _stempLab.textAlignment = NSTextAlignmentCenter; 101 | } 102 | return _stempLab; 103 | } 104 | 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /Teleprompter_iOS/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Teleprompter_iOS/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | 12 | @end 13 | 14 | @implementation AppDelegate 15 | 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | 23 | #pragma mark - UISceneSession lifecycle 24 | 25 | 26 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 27 | // Called when a new scene session is being created. 28 | // Use this method to select a configuration to create the new scene with. 29 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 30 | } 31 | 32 | 33 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 34 | // Called when the user discards a scene session. 35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Teleprompter_iOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Teleprompter_iOS/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 | -------------------------------------------------------------------------------- /Teleprompter_iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Teleprompter_iOS/BackgroundTaskManager/BackgroundTaskManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundTaskManager.h 3 | // Teleprompter_iOS 4 | // 5 | // Created by ileja on 2022/5/17. 6 | // 7 | // 后台保活工具 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface BackgroundTaskManager : NSObject 14 | 15 | /// 创建单利 16 | + (BackgroundTaskManager *)shareManager; 17 | 18 | /// 开始播放音乐 19 | - (void)startPlayAudioSession; 20 | 21 | /// 停止播放音乐 22 | - (void)stopPlayAudioSession; 23 | 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /Teleprompter_iOS/BackgroundTaskManager/BackgroundTaskManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundTaskManager.m 3 | // Teleprompter_iOS 4 | // 5 | // Created by ileja on 2022/5/17. 6 | // 7 | 8 | #import "BackgroundTaskManager.h" 9 | #import 10 | #import 11 | 12 | @interface BackgroundTaskManager() 13 | 14 | @property (strong, nonatomic) AVAudioPlayer *audioPlayer; 15 | 16 | @end 17 | 18 | @implementation BackgroundTaskManager 19 | 20 | /// 创建单利 21 | + (BackgroundTaskManager *)shareManager 22 | { 23 | static BackgroundTaskManager *manager = nil; 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | manager = [[BackgroundTaskManager alloc]init]; 27 | }); 28 | return manager; 29 | } 30 | 31 | /// 开始播放声音 32 | - (void)startPlayAudioSession 33 | { 34 | [self.audioPlayer play]; 35 | } 36 | 37 | /// 停止播放声音 38 | - (void)stopPlayAudioSession 39 | { 40 | [self.audioPlayer stop]; 41 | } 42 | 43 | - (AVAudioPlayer *)audioPlayer 44 | { 45 | if (!_audioPlayer) { 46 | //设置后台模式和锁屏模式下依然能够播放 47 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil]; 48 | [[AVAudioSession sharedInstance] setActive: YES error: nil]; 49 | //初始化音频播放器 50 | NSError *playerError; 51 | NSURL *urlSound = [[NSURL alloc]initWithString:[[NSBundle mainBundle]pathForResource:@"pomodoSound" ofType:@"wav"]]; 52 | _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:urlSound error:&playerError]; 53 | _audioPlayer.numberOfLoops = -1;//无限播放 54 | _audioPlayer.volume = 0; 55 | } 56 | return _audioPlayer; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Teleprompter_iOS/BackgroundTaskManager/pomodoSound.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuzheng0301/Teleprompter_iOS/b70fdd3fc263a5a19846f22f0620cbd3035339c5/Teleprompter_iOS/BackgroundTaskManager/pomodoSound.wav -------------------------------------------------------------------------------- /Teleprompter_iOS/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 | -------------------------------------------------------------------------------- /Teleprompter_iOS/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 | -------------------------------------------------------------------------------- /Teleprompter_iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | UIBackgroundModes 25 | 26 | audio 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Teleprompter_iOS/PlayscriptTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PlayscriptTextView.h 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/28. 6 | // 7 | // 台词本控件 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | #define ScreenWidth [UIScreen mainScreen].bounds.size.width 14 | #define ScreenHeight [UIScreen mainScreen].bounds.size.height 15 | 16 | @interface PlayscriptTextView : UIView 17 | 18 | @property (nonatomic, strong) UITextView *textV; 19 | 20 | /// TextView内容 21 | @property (nonatomic, strong) NSString *textString; 22 | 23 | /// 内容字号 24 | @property (nonatomic, strong) UIFont *textFont; 25 | 26 | - (void)resetFrame:(CGRect)frame; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /Teleprompter_iOS/PlayscriptTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PlayscriptTextView.m 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/28. 6 | // 7 | 8 | #import "PlayscriptTextView.h" 9 | 10 | @interface PlayscriptTextView () 11 | 12 | @property (nonatomic, strong) UILabel *lineLab; 13 | @property (nonatomic, strong) UIView *shutterView;//遮板 14 | 15 | @end 16 | 17 | @implementation PlayscriptTextView 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame 20 | { 21 | self = [super initWithFrame:frame]; 22 | if (self) { 23 | 24 | [self createUIWithFrame:frame]; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)createUIWithFrame:(CGRect)frame 30 | { 31 | [self addSubview:self.textV]; 32 | 33 | [self addSubview:self.lineLab]; 34 | 35 | [self addSubview:self.shutterView]; 36 | } 37 | 38 | /// 重新设置控件大小 39 | - (void)resetFrame:(CGRect)frame 40 | { 41 | self.textV.frame = CGRectMake(0, 0, frame.size.width, frame.size.height); 42 | self.shutterView.frame = CGRectMake(0, 0, frame.size.width, 30); 43 | self.lineLab.frame = CGRectMake(0, 30, frame.size.width, 1); 44 | } 45 | 46 | /// 设置内容 47 | - (void)setTextString:(NSString *)textString 48 | { 49 | _textString = textString; 50 | 51 | self.textV.text = textString; 52 | } 53 | /// 设置内容字号 54 | - (void)setTextFont:(UIFont *)textFont 55 | { 56 | _textFont = textFont; 57 | 58 | self.textV.font = textFont; 59 | } 60 | 61 | 62 | #pragma mark --- lazyload 63 | - (UITextView *)textV 64 | { 65 | if (!_textV) { 66 | _textV = [[UITextView alloc]initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)]; 67 | _textV.scrollEnabled= YES; 68 | _textV.editable = NO; 69 | _textV.textColor = [UIColor darkGrayColor]; 70 | _textV.layoutManager.allowsNonContiguousLayout= NO; 71 | } 72 | return _textV; 73 | } 74 | - (UIView *)shutterView 75 | { 76 | if (!_shutterView) { 77 | _shutterView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 30)]; 78 | _shutterView.alpha = 0.1; 79 | _shutterView.backgroundColor = [UIColor blackColor]; 80 | } 81 | return _shutterView; 82 | } 83 | - (UILabel *)lineLab 84 | { 85 | if (!_lineLab) { 86 | _lineLab = [[UILabel alloc]initWithFrame:CGRectMake(0, 30, self.bounds.size.width, 1)]; 87 | _lineLab.backgroundColor = [UIColor redColor]; 88 | } 89 | return _lineLab; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /Teleprompter_iOS/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import 9 | 10 | @interface SceneDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow * window; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Teleprompter_iOS/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import "SceneDelegate.h" 9 | #import "BackgroundTaskManager.h" 10 | 11 | @interface SceneDelegate () 12 | 13 | @property UIBackgroundTaskIdentifier backgroundTask; 14 | 15 | @end 16 | 17 | @implementation SceneDelegate 18 | 19 | 20 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 21 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 22 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 23 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 24 | } 25 | 26 | 27 | - (void)sceneDidDisconnect:(UIScene *)scene { 28 | // Called as the scene is being released by the system. 29 | // This occurs shortly after the scene enters the background, or when its session is discarded. 30 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 31 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 32 | } 33 | 34 | 35 | - (void)sceneDidBecomeActive:(UIScene *)scene { 36 | // Called when the scene has moved from an inactive state to an active state. 37 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 38 | 39 | //停止播放 40 | [[BackgroundTaskManager shareManager] stopPlayAudioSession]; 41 | } 42 | 43 | 44 | - (void)sceneWillResignActive:(UIScene *)scene { 45 | // Called when the scene will move from an active state to an inactive state. 46 | // This may occur due to temporary interruptions (ex. an incoming phone call). 47 | } 48 | 49 | 50 | - (void)sceneWillEnterForeground:(UIScene *)scene { 51 | // Called as the scene transitions from the background to the foreground. 52 | // Use this method to undo the changes made on entering the background. 53 | } 54 | 55 | 56 | - (void)sceneDidEnterBackground:(UIScene *)scene { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | 61 | //开始播放 62 | [[BackgroundTaskManager shareManager] startPlayAudioSession]; 63 | } 64 | 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /Teleprompter_iOS/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Teleprompter_iOS/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import "ViewController.h" 9 | #import 10 | #import "AdjustSliderView.h" 11 | #import "PlayscriptTextView.h" 12 | 13 | #define ScreenWidth [UIScreen mainScreen].bounds.size.width 14 | #define ScreenHeight [UIScreen mainScreen].bounds.size.height 15 | 16 | #define textSize 30 //初始字号 17 | #define moveSpeed 10 //初始速度 18 | 19 | @interface ViewController () 20 | { 21 | NSInteger speedInt;//速度记录 22 | NSInteger sizeInt;//字号记录 23 | UIWindow *firstWindow;//画中画 24 | 25 | NSTimer *timer; 26 | NSTimer *pipTimer; 27 | } 28 | @property (nonatomic, strong) PlayscriptTextView *textView;//页面展示 29 | @property (nonatomic, strong) PlayscriptTextView *pipTextView;//画中画展示 30 | @property (nonatomic, strong) UILabel *placeholderLab;//开启画中画占位 31 | @property (nonatomic, strong) UIButton *startBtn;//开启画中画按钮 32 | 33 | @property (nonatomic, strong) AVPlayer *avPlayer; 34 | @property (nonatomic, strong) AVPlayerLayer *playerLayer;//播放内容 35 | @property (nonatomic, strong) AVPictureInPictureController *pipVC;//画中画控制器 36 | 37 | //字体大小 38 | @property (nonatomic, strong) AdjustSliderView *sizeSlider; 39 | //速度 40 | @property (nonatomic, strong) AdjustSliderView *speedSlider; 41 | 42 | @end 43 | 44 | @implementation ViewController 45 | 46 | - (void)viewDidLoad { 47 | [super viewDidLoad]; 48 | // Do any additional setup after loading the view. 49 | 50 | [self performSelector:@selector(resetTextWithInterval:) withObject:nil afterDelay:0.5f]; 51 | 52 | //画中画功能 53 | NSError *error = nil; 54 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error]; 55 | [[AVAudioSession sharedInstance] setActive:YES error:&error]; 56 | 57 | self.pipVC = [[AVPictureInPictureController alloc] initWithPlayerLayer:self.playerLayer]; 58 | self.pipVC.delegate = self; 59 | [self.pipVC setValue:@1 forKey:@"controlsStyle"]; 60 | 61 | [self.view addSubview:self.textView]; 62 | 63 | [self.view addSubview:self.startBtn]; 64 | 65 | //字号 66 | [self.view addSubview:self.sizeSlider]; 67 | //速度 68 | [self.view addSubview:self.speedSlider]; 69 | } 70 | 71 | //设置、重置滚动速度 72 | - (void)resetTextWithInterval:(float)timeInterval 73 | { 74 | if (timeInterval == 0 && speedInt == 0) { 75 | timeInterval = 0.06; 76 | } else if (timeInterval == 0 && speedInt != 0) { 77 | timeInterval = 0.6/speedInt; 78 | } 79 | [timer invalidate]; 80 | timer = nil; 81 | timer = [NSTimer scheduledTimerWithTimeInterval: timeInterval target: self selector:@selector(onTick:) userInfo: nil repeats:YES]; 82 | } 83 | 84 | - (void)onTick:(NSTimer*)theTimer 85 | { 86 | CGPoint pt = [self.textView.textV contentOffset]; 87 | CGFloat n = pt.y + 1; 88 | [self.textView.textV setContentOffset:CGPointMake(pt.x, n)]; 89 | 90 | if (n > (self.textView.textV.contentSize.height)) { 91 | [theTimer invalidate]; 92 | theTimer = nil; 93 | [timer invalidate]; 94 | timer = nil; 95 | //滚动回顶部重新开始 96 | [self.textView.textV scrollRangeToVisible:NSMakeRange(0, 1)]; 97 | [self performSelector:@selector(resetTextWithInterval:) withObject:nil afterDelay:0.5f]; 98 | } 99 | } 100 | 101 | - (void)resetPipTextWithInterval:(float)timeInterval 102 | { 103 | if (timeInterval == 0 && speedInt == 0) { 104 | timeInterval = 0.06; 105 | } else if (timeInterval == 0 && speedInt != 0) { 106 | timeInterval = 0.6/speedInt; 107 | } 108 | [pipTimer invalidate]; 109 | pipTimer = nil; 110 | pipTimer = [NSTimer scheduledTimerWithTimeInterval: timeInterval target: self selector:@selector(pipOnTick:) userInfo: nil repeats:YES]; 111 | } 112 | 113 | - (void)pipOnTick:(NSTimer *)theTimer 114 | { 115 | CGPoint pipPt = [self.pipTextView.textV contentOffset]; 116 | CGFloat pipN = pipPt.y + 1; 117 | [self.pipTextView.textV setContentOffset:CGPointMake(pipPt.x, pipN)]; 118 | 119 | if (pipN > (self.pipTextView.textV.contentSize.height)) { 120 | [theTimer invalidate]; 121 | theTimer = nil; 122 | [pipTimer invalidate]; 123 | pipTimer = nil; 124 | //滚动回顶部重新开始 125 | [self.pipTextView.textV scrollRangeToVisible:NSMakeRange(0, 1)]; 126 | [self performSelector:@selector(resetPipTextWithInterval:) withObject:nil afterDelay:0.5f]; 127 | } 128 | } 129 | 130 | #pragma mark --- button点击事件 131 | - (void)didClickStartBtn 132 | { 133 | //判断是否支持画中画功能 134 | if ([AVPictureInPictureController isPictureInPictureSupported]) { 135 | if (self.pipVC.isPictureInPictureActive) { 136 | [self.startBtn setTitle:@"开始提词" forState:UIControlStateNormal]; 137 | [self.pipVC stopPictureInPicture]; 138 | } else { 139 | [self.startBtn setTitle:@"结束提词" forState:UIControlStateNormal]; 140 | [self.pipVC startPictureInPicture]; 141 | } 142 | } 143 | } 144 | 145 | #pragma mark --- 画中画代理 146 | // 即将开启画中画 147 | - (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController 148 | { 149 | //开始计时 150 | [self performSelector:@selector(resetPipTextWithInterval:) withObject:nil afterDelay:0.5f]; 151 | 152 | firstWindow = [UIApplication sharedApplication].windows.firstObject; 153 | //添加KVO监听大小改变 154 | [firstWindow addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; 155 | 156 | [self.pipTextView resetFrame:CGRectMake(0, 0, firstWindow.bounds.size.width, firstWindow.bounds.size.height)]; 157 | [firstWindow addSubview:self.pipTextView]; 158 | } 159 | // 已经开启画中画 160 | - (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController 161 | { 162 | //先加遮盖 163 | [self.playerLayer addSublayer:self.placeholderLab.layer]; 164 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 165 | // 延迟0.2秒隐藏防止有系统页面闪过 166 | self.textView.hidden = YES; 167 | }); 168 | } 169 | // 开启画中画失败 170 | - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error 171 | { 172 | 173 | } 174 | // 即将关闭画中画 175 | - (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController 176 | { 177 | self.textView.hidden = NO; 178 | } 179 | // 已经关闭画中画 180 | - (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController 181 | { 182 | [pipTimer invalidate]; 183 | pipTimer = nil; 184 | //滚动回顶部重新开始 185 | [self.pipTextView.textV scrollRangeToVisible:NSMakeRange(0, 1)]; 186 | } 187 | // 关闭画中画且恢复播放界面 188 | - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler 189 | { 190 | completionHandler(YES); 191 | } 192 | 193 | //frame改变 194 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 195 | { 196 | // keypath 197 | if ([keyPath isEqualToString:@"frame"]) { 198 | [self.pipTextView resetFrame:CGRectMake(0, 0, firstWindow.bounds.size.width, firstWindow.bounds.size.height)]; 199 | } 200 | } 201 | 202 | #pragma mark --- slider改变 203 | - (void)valueChanged:(UISlider *)slider 204 | { 205 | if (slider == self.sizeSlider.stempSlider) { 206 | //字号 207 | self.sizeSlider.sliderValue = slider.value; 208 | sizeInt = slider.value; 209 | self.textView.textFont = [UIFont systemFontOfSize:sizeInt]; 210 | self.pipTextView.textFont = [UIFont systemFontOfSize:sizeInt]; 211 | } else if (slider == self.speedSlider.stempSlider) { 212 | //速度 213 | self.speedSlider.sliderValue = slider.value; 214 | speedInt = slider.value; 215 | [self resetTextWithInterval:0.5/slider.value]; 216 | [self resetPipTextWithInterval:0.5/slider.value]; 217 | } 218 | } 219 | 220 | 221 | #pragma mark --- lazyload 222 | - (PlayscriptTextView *)textView 223 | { 224 | if (!_textView) { 225 | _textView = [[PlayscriptTextView alloc]initWithFrame:CGRectMake(30, 60, ScreenWidth-60, (ScreenWidth-60)*0.75)]; 226 | _textView.textString = @"测试文本Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.\nUse this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.测试文本测试文本测试文本测试文本测试文本lly configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.\nUse this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.测试文本测试文本测试文本测试文本测试文本 UIWindowScene `scene`.测试文本测试文本测试文本测试文本测试文本lly configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.\nUse this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.测试文本测试文本测试文本测试文本测试文本"; 227 | _textView.textFont = [UIFont systemFontOfSize:textSize]; 228 | _textView.layer.cornerRadius = 10.0; 229 | _textView.layer.masksToBounds = YES; 230 | _textView.textV.layer.cornerRadius = 10.0; 231 | _textView.textV.layer.borderWidth = 1; 232 | _textView.textV.layer.borderColor = [UIColor lightGrayColor].CGColor; 233 | } 234 | return _textView; 235 | } 236 | - (PlayscriptTextView *)pipTextView 237 | { 238 | if (!_pipTextView) { 239 | _pipTextView = [[PlayscriptTextView alloc]init]; 240 | _pipTextView.textString = @"测试文本Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.\nUse this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.测试文本测试文本测试文本测试文本测试文本lly configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.\nUse this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.测试文本测试文本测试文本测试文本测试文本 UIWindowScene `scene`.测试文本测试文本测试文本测试文本测试文本lly configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.\nUse this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.测试文本测试文本测试文本测试文本测试文本"; 241 | _pipTextView.textFont = [UIFont systemFontOfSize:textSize]; 242 | } 243 | return _pipTextView; 244 | } 245 | - (UILabel *)placeholderLab 246 | { 247 | if (!_placeholderLab) { 248 | _placeholderLab = [[UILabel alloc]initWithFrame:CGRectMake(0, -1, ScreenWidth-60, (ScreenWidth-60)*0.75+2)]; 249 | _placeholderLab.backgroundColor = [UIColor whiteColor]; 250 | _placeholderLab.textAlignment = NSTextAlignmentCenter; 251 | _placeholderLab.textColor = [UIColor lightGrayColor]; 252 | _placeholderLab.font = [UIFont systemFontOfSize:13.0]; 253 | _placeholderLab.numberOfLines = 0; 254 | } 255 | return _placeholderLab; 256 | } 257 | - (UIButton *)startBtn 258 | { 259 | if (!_startBtn) { 260 | _startBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 261 | _startBtn.frame = CGRectMake(ScreenWidth/2-60, CGRectGetMaxY(self.textView.frame)+35, 120, 50); 262 | _startBtn.layer.borderColor = [UIColor darkGrayColor].CGColor; 263 | _startBtn.layer.borderWidth = 1; 264 | _startBtn.layer.cornerRadius = 25.0; 265 | [_startBtn setTitle:@"开始提词" forState:UIControlStateNormal]; 266 | [_startBtn addTarget:self action:@selector(didClickStartBtn) forControlEvents:UIControlEventTouchUpInside]; 267 | } 268 | return _startBtn; 269 | } 270 | 271 | - (AVPlayerLayer *)playerLayer { 272 | if (!_playerLayer) { 273 | NSString *path = [[NSBundle mainBundle] pathForResource:@"holder" ofType:@"mp4"]; 274 | NSURL *sourceMovieUrl = [NSURL fileURLWithPath:path]; 275 | self.avPlayer = [[AVPlayer alloc]initWithURL:sourceMovieUrl]; 276 | _playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; 277 | _playerLayer.frame = CGRectMake(30, 60, ScreenWidth-60, (ScreenWidth-60)*0.75); 278 | _playerLayer.backgroundColor = [UIColor blackColor].CGColor; 279 | _playerLayer.videoGravity = AVLayerVideoGravityResizeAspect; 280 | _playerLayer.cornerRadius = 10.0; 281 | _playerLayer.masksToBounds = YES; 282 | [self.view.layer addSublayer:self.playerLayer]; 283 | } 284 | return _playerLayer; 285 | } 286 | 287 | //字体大小 288 | - (AdjustSliderView *)sizeSlider 289 | { 290 | if (!_sizeSlider) { 291 | _sizeSlider = [[AdjustSliderView alloc]initWithFrame:CGRectMake(10, ScreenHeight-100, ScreenWidth-20, 30)]; 292 | _sizeSlider.sliderValue = textSize; 293 | _sizeSlider.maximumValue = 80; 294 | _sizeSlider.minimumValue = 20; 295 | _sizeSlider.title = @"字号"; 296 | [_sizeSlider sliderAddTarget:self action:@selector(valueChanged:) forControlEvents:(UIControlEventValueChanged)]; 297 | } 298 | return _sizeSlider; 299 | } 300 | 301 | //速度 302 | - (AdjustSliderView *)speedSlider 303 | { 304 | if (!_speedSlider) { 305 | _speedSlider = [[AdjustSliderView alloc]initWithFrame:CGRectMake(10, ScreenHeight-140, ScreenWidth-20, 30)]; 306 | _speedSlider.maximumValue = 40; 307 | _speedSlider.minimumValue = 5; 308 | _speedSlider.sliderValue = moveSpeed; 309 | _speedSlider.title = @"速度"; 310 | [_speedSlider sliderAddTarget:self action:@selector(valueChanged:) forControlEvents:(UIControlEventValueChanged)]; 311 | } 312 | return _speedSlider; 313 | } 314 | 315 | @end 316 | -------------------------------------------------------------------------------- /Teleprompter_iOS/holder.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuzheng0301/Teleprompter_iOS/b70fdd3fc263a5a19846f22f0620cbd3035339c5/Teleprompter_iOS/holder.mp4 -------------------------------------------------------------------------------- /Teleprompter_iOS/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Teleprompter_iOS 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) { 12 | NSString * appDelegateClassName; 13 | @autoreleasepool { 14 | // Setup code that might create autoreleased objects goes here. 15 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 16 | } 17 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 18 | } 19 | -------------------------------------------------------------------------------- /Teleprompter_iOSTests/Teleprompter_iOSTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // Teleprompter_iOSTests.m 3 | // Teleprompter_iOSTests 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import 9 | 10 | @interface Teleprompter_iOSTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation Teleprompter_iOSTests 15 | 16 | - (void)setUp { 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | - (void)tearDown { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | } 23 | 24 | - (void)testExample { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | - (void)testPerformanceExample { 30 | // This is an example of a performance test case. 31 | [self measureBlock:^{ 32 | // Put the code you want to measure the time of here. 33 | }]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Teleprompter_iOSUITests/Teleprompter_iOSUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // Teleprompter_iOSUITests.m 3 | // Teleprompter_iOSUITests 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import 9 | 10 | @interface Teleprompter_iOSUITests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation Teleprompter_iOSUITests 15 | 16 | - (void)setUp { 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | 19 | // In UI tests it is usually best to stop immediately when a failure occurs. 20 | self.continueAfterFailure = NO; 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | - (void)tearDown { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | - (void)testExample { 30 | // UI tests must launch the application that they test. 31 | XCUIApplication *app = [[XCUIApplication alloc] init]; 32 | [app launch]; 33 | 34 | // Use recording to get started writing UI tests. 35 | // Use XCTAssert and related functions to verify your tests produce the correct results. 36 | } 37 | 38 | - (void)testLaunchPerformance { 39 | if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *)) { 40 | // This measures how long it takes to launch your application. 41 | [self measureWithMetrics:@[[[XCTApplicationLaunchMetric alloc] init]] block:^{ 42 | [[[XCUIApplication alloc] init] launch]; 43 | }]; 44 | } 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Teleprompter_iOSUITests/Teleprompter_iOSUITestsLaunchTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // Teleprompter_iOSUITestsLaunchTests.m 3 | // Teleprompter_iOSUITests 4 | // 5 | // Created by fz on 2022/2/25. 6 | // 7 | 8 | #import 9 | 10 | @interface Teleprompter_iOSUITestsLaunchTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation Teleprompter_iOSUITestsLaunchTests 15 | 16 | + (BOOL)runsForEachTargetApplicationUIConfiguration { 17 | return YES; 18 | } 19 | 20 | - (void)setUp { 21 | self.continueAfterFailure = NO; 22 | } 23 | 24 | - (void)testLaunch { 25 | XCUIApplication *app = [[XCUIApplication alloc] init]; 26 | [app launch]; 27 | 28 | // Insert steps here to perform after app launch but before taking a screenshot, 29 | // such as logging into a test account or navigating somewhere in the app 30 | 31 | XCTAttachment *attachment = [XCTAttachment attachmentWithScreenshot:XCUIScreen.mainScreen.screenshot]; 32 | attachment.name = @"Launch Screen"; 33 | attachment.lifetime = XCTAttachmentLifetimeKeepAlways; 34 | [self addAttachment:attachment]; 35 | } 36 | 37 | @end 38 | --------------------------------------------------------------------------------