├── README.md ├── saveCover.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── chengzhaohua.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── chengzhaohua.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── saveCover ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── back.imageset │ │ ├── Contents.json │ │ ├── icon_back@2x.png │ │ └── icon_back@3x.png │ ├── slider_deselect.imageset │ │ ├── Contents.json │ │ ├── btn_n_cover@2x.png │ │ └── btn_n_cover@3x.png │ └── slider_select.imageset │ │ ├── Contents.json │ │ ├── btn_p_cover@2x.png │ │ └── btn_p_cover@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CZHChooseCover │ ├── CZHChooseCoverCell.h │ ├── CZHChooseCoverCell.m │ ├── CZHChooseCoverController.h │ ├── CZHChooseCoverController.m │ ├── CZHTool.h │ ├── CZHTool.m │ ├── Header.h │ ├── UIButton+Extension.h │ ├── UIButton+Extension.m │ ├── UILabel+Extension.h │ └── UILabel+Extension.m ├── Info.plist ├── MBProgressHUD │ ├── MBProgressHUD.h │ └── MBProgressHUD.m ├── ViewController.h ├── ViewController.m ├── main.m └── video │ └── haha.mp4 ├── saveCoverTests ├── Info.plist └── saveCoverTests.m └── saveCoverUITests ├── Info.plist └── saveCoverUITests.m /README.md: -------------------------------------------------------------------------------- 1 | # CZHChooseCoverController 2 | 3 | 4 | 5 | ![合成 1.gif](http://upload-images.jianshu.io/upload_images/6709174-f9c9f8667d18c705.gif?imageMogr2/auto-orient/strip) 6 | 7 | ###### 起初看到这个功能我是拒绝的,之前做的视频上传都是获取特定的帧数当封面,没有刻意的去选择封面,但是需求已定,随后网上也找了下,没有类似的,于是乎就自己写了一个,有什么改进的地方可以互相交流,话不多说直接上代码了 8 | 9 | 1.打开相册,系统相册用的很顺手,所以一直就用系统的相册 10 | ``` 11 | //两个代理 12 | @interface ViewController () 13 | 14 | @property (weak, nonatomic) IBOutlet UIImageView *coverImageView; 15 | 16 | @end 17 | ``` 18 | ``` 19 | //代理方法 20 | //打开相册 21 | - (void)openImagePickerController:(UIImagePickerControllerSourceType)type 22 | { 23 | if (![UIImagePickerController isSourceTypeAvailable:type]) return; 24 | 25 | UIImagePickerController *ipc = [[UIImagePickerController alloc] init]; 26 | 27 | //视频最大时间 28 | ipc.videoMaximumDuration = 30; 29 | ipc.view.backgroundColor = [UIColor whiteColor]; 30 | ipc.sourceType = type; 31 | ipc.delegate = self; 32 | //只打开视频 33 | ipc.mediaTypes = @[(NSString *)kUTTypeMovie]; 34 | //视频上传质量 35 | ipc.videoQuality = UIImagePickerControllerQualityTypeHigh; 36 | [self presentViewController:ipc animated:YES completion:nil]; 37 | } 38 | 39 | 40 | #pragma mark - UIImagePickerControllerDelegate 41 | /** 42 | * 从UIImagePickerController选择完图片后就调用(拍照完毕或者选择相册图片完毕) 43 | */ 44 | 45 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 46 | 47 | CZHWeakSelf(self); 48 | 49 | NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType]; 50 | 51 | if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) { 52 | //如果是视频 53 | NSURL *url = info[UIImagePickerControllerMediaURL]; 54 | //计算相册视频时长 55 | NSDictionary *videoDic = [CZHTool getLocalVideoSizeAndTimeWithSourcePath:url.absoluteString]; 56 | int videoTime = [[videoDic valueForKey:@"duration"] intValue]; 57 | //视频限制的长度 58 | NSUInteger limitTime = 30; 59 | if (videoTime > limitTime) { 60 | [picker dismissViewControllerAnimated:YES completion:nil]; 61 | return; 62 | } 63 | //把系统相册mov格式转换成mp4格式 64 | [CZHTool convertMovTypeIntoMp4TypeWithSourceUrl:url convertSuccess:^(NSURL *path) { 65 | CZHStrongSelf(self); 66 | [picker dismissViewControllerAnimated:YES completion:nil]; 67 | //选择封面控制器 68 | CZHChooseCoverController *chooseCover = [[CZHChooseCoverController alloc] init]; 69 | //本地路径 70 | chooseCover.videoPath = path; 71 | //选择封面的block,把封面image回调 72 | chooseCover.coverImageBlock = ^(UIImage *coverImage) { 73 | self.coverImageView.image = coverImage; 74 | 75 | //上传视频操作 76 | //[self changeWithUploadSource:path]; 77 | //上传封面操作 78 | //[self uploadCoverWithImage:coverImage]; 79 | }; 80 | [self presentViewController:chooseCover animated:YES completion:nil]; 81 | }]; 82 | } 83 | } 84 | 85 | // 取消图片选择调用此方法 86 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { 87 | // dismiss UIImagePickerController 88 | [self dismissViewControllerAnimated:YES completion:nil]; 89 | } 90 | ``` 91 | 92 | 2.选择封面控制器 93 | ``` 94 | //截取几张图片放在底部用作展示,我是用collectionview做展示 95 | AVURLAsset * asset = [AVURLAsset assetWithURL:self.videoPath]; 96 | CMTime time = [asset duration]; 97 | self.timeValue = time.value; 98 | self.timeScale = time.timescale; 99 | if (time.value < 1) { 100 | [self dismissViewControllerAnimated:YES completion:nil]; 101 | return; 102 | } 103 | 104 | NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]; 105 | AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:self.videoPath options:opts]; 106 | AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset]; 107 | generator.appliesPreferredTrackTransform = YES; 108 | /**The actual time of the generated images will be within the range [requestedTime-toleranceBefore, requestedTime+toleranceAfter] and may differ from the requested time for efficiency. 109 | Pass kCMTimeZero for both toleranceBefore and toleranceAfter to request frame-accurate image generation; this may incur additional decoding delay. 110 | Default is kCMTimePositiveInfinity.*/ 111 | 112 | generator.requestedTimeToleranceAfter = kCMTimeZero; 113 | generator.requestedTimeToleranceBefore = kCMTimeZero; 114 | //总帧数/展示的总数量 115 | long long baseCount = time.value / PHOTP_COUNT; 116 | //取出PHOTP_COUNT张图片,存放到数组,用于collectionview 117 | for (NSInteger i = 0 ; i < PHOTP_COUNT; i++) { 118 | 119 | NSError *error = nil; 120 | //每隔baseCount帧取一帧存起来,一共PHOTP_COUNT张 121 | CGImageRef img = [generator copyCGImageAtTime:CMTimeMake(i * baseCount, time.timescale) actualTime:NULL error:&error]; 122 | { 123 | UIImage *image = [UIImage imageWithCGImage:img]; 124 | 125 | [self.photoArrays addObject:image]; 126 | } 127 | ///释放内存 128 | CGImageRelease(img); 129 | } 130 | ``` 131 | 132 | ``` 133 | //把存的存的几张图片用collectionview展示出来 134 | CGRect collectionViewF = CGRectMake(0, CZH_ScaleHeight(461), ScreenWidth, CZH_ScaleHeight(62.5)); 135 | UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:collectionViewF collectionViewLayout:layout]; 136 | collectionView.dataSource = self; 137 | collectionView.backgroundColor = [UIColor clearColor]; 138 | [collectionView registerClass:[CZHChooseCoverCell class] forCellWithReuseIdentifier:ID]; 139 | [self.view addSubview:collectionView]; 140 | self.collectionView = collectionView; 141 | 142 | //在collectionview上面覆盖一个slider用于滑动选择图片 143 | UIImage *selected = [UIImage imageNamed:@"slider_select"]; 144 | UIImage *deselected = [UIImage imageNamed:@"slider_deselect"]; 145 | 146 | UISlider *slider = [[UISlider alloc] init]; 147 | slider.frame = CGRectMake(0, CZH_ScaleHeight(461), ScreenWidth, CZH_ScaleHeight(62.5)); 148 | 149 | [slider setThumbImage:deselected forState:UIControlStateNormal]; 150 | [slider setThumbImage:selected forState:UIControlStateHighlighted]; 151 | //透明的图片 152 | UIImage *image = [CZHTool imageWithColor:[UIColor clearColor] size:CGSizeMake(1, 1)]; 153 | 154 | [slider setMinimumTrackImage:image forState:UIControlStateNormal]; 155 | [slider setMaximumTrackImage:image forState:UIControlStateNormal]; 156 | 157 | slider.maximumValue = self.timeValue; 158 | slider.minimumValue = 0; 159 | [slider addTarget:self action:@selector(valueChange:) forControlEvents:UIControlEventValueChanged]; 160 | [self.view addSubview:slider]; 161 | 162 | //默认选取第一帧展示 163 | [self chooseWithTime:0]; 164 | 165 | ``` 166 | 167 | ``` 168 | //滑动slider的操作 169 | - (void)valueChange:(UISlider *)sender { 170 | 171 | int timeValue = sender.value; 172 | 173 | [self chooseWithTime:timeValue]; 174 | } 175 | 176 | 177 | - (void)chooseWithTime:(CMTimeValue)time { 178 | 179 | NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]; 180 | AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:self.videoPath options:opts]; 181 | AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset]; 182 | generator.appliesPreferredTrackTransform = YES; 183 | /**The actual time of the generated images will be within the range [requestedTime-toleranceBefore, requestedTime+toleranceAfter] and may differ from the requested time for efficiency. 184 | Pass kCMTimeZero for both toleranceBefore and toleranceAfter to request frame-accurate image generation; this may incur additional decoding delay. 185 | Default is kCMTimePositiveInfinity.*/ 186 | 187 | generator.requestedTimeToleranceAfter = kCMTimeZero; 188 | generator.requestedTimeToleranceBefore = kCMTimeZero; 189 | 190 | 191 | NSError *error = nil; 192 | CGImageRef img = [generator copyCGImageAtTime:CMTimeMake(time, self.timeScale) actualTime:NULL error:&error]; 193 | { 194 | UIImage *image = [UIImage imageWithCGImage:img]; 195 | 196 | self.imageView.image = image; 197 | } 198 | ///释放内存 199 | CGImageRelease(img); 200 | } 201 | 202 | ``` 203 | 204 | ``` 205 | //点击返回按钮和完成按钮的操作 206 | - (void)buttonClick:(UIButton *)sender { 207 | if (sender.tag == CZHChooseCoverControllerButtonTypeBack) { 208 | [self dismissViewControllerAnimated:YES completion:nil]; 209 | } else if (sender.tag == CZHChooseCoverControllerButtonTypeComplete) { 210 | //封面图片回调 211 | if (_coverImageBlock) { 212 | _coverImageBlock(self.imageView.image); 213 | } 214 | [self dismissViewControllerAnimated:YES completion:nil]; 215 | } 216 | } 217 | ``` 218 | 219 | 220 | ps.模拟器相册没有视频的话可以用下面代码把项目的视频保存到相册 221 | ``` 222 | NSMutableArray *videoArray = [NSMutableArray array]; 223 | //工程中类型是MP4的文件数组 224 | NSArray *movs = [[NSBundle mainBundle] pathsForResourcesOfType:@"mp4" inDirectory:nil]; 225 | [videoArray addObjectsFromArray:movs]; 226 | for (id item in videoArray) { 227 | //循环保存到相册 228 | if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(item)) { 229 | UISaveVideoAtPathToSavedPhotosAlbum(item, self, nil, NULL); 230 | } 231 | } 232 | ``` 233 | 234 | [更多查看blog](http://blog.csdn.net/HurryUpCheng) 235 | -------------------------------------------------------------------------------- /saveCover.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 590C4D1A1FA6BD4500B9B11A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D191FA6BD4500B9B11A /* AppDelegate.m */; }; 11 | 590C4D1D1FA6BD4500B9B11A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D1C1FA6BD4500B9B11A /* ViewController.m */; }; 12 | 590C4D201FA6BD4500B9B11A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 590C4D1E1FA6BD4500B9B11A /* Main.storyboard */; }; 13 | 590C4D221FA6BD4500B9B11A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 590C4D211FA6BD4500B9B11A /* Assets.xcassets */; }; 14 | 590C4D251FA6BD4500B9B11A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 590C4D231FA6BD4500B9B11A /* LaunchScreen.storyboard */; }; 15 | 590C4D281FA6BD4500B9B11A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D271FA6BD4500B9B11A /* main.m */; }; 16 | 590C4D321FA6BD4500B9B11A /* saveCoverTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D311FA6BD4500B9B11A /* saveCoverTests.m */; }; 17 | 590C4D3D1FA6BD4500B9B11A /* saveCoverUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D3C1FA6BD4500B9B11A /* saveCoverUITests.m */; }; 18 | 590C4D501FA6C7F400B9B11A /* CZHTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D4F1FA6C7F400B9B11A /* CZHTool.m */; }; 19 | 590C4D551FA6CA4400B9B11A /* CZHChooseCoverController.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D541FA6CA4400B9B11A /* CZHChooseCoverController.m */; }; 20 | 590C4D591FA6CBEA00B9B11A /* MBProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D581FA6CBEA00B9B11A /* MBProgressHUD.m */; }; 21 | 590C4D5C1FA6CCE500B9B11A /* UILabel+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D5B1FA6CCE500B9B11A /* UILabel+Extension.m */; }; 22 | 590C4D5F1FA6CDFA00B9B11A /* CZHChooseCoverCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D5E1FA6CDFA00B9B11A /* CZHChooseCoverCell.m */; }; 23 | 590C4D621FA6CE9D00B9B11A /* UIButton+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 590C4D611FA6CE9D00B9B11A /* UIButton+Extension.m */; }; 24 | 59DC4C8B1FDE898B00914721 /* haha.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 59DC4C8A1FDE898B00914721 /* haha.mp4 */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | 590C4D2E1FA6BD4500B9B11A /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 590C4D0D1FA6BD4500B9B11A /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 590C4D141FA6BD4500B9B11A; 33 | remoteInfo = saveCover; 34 | }; 35 | 590C4D391FA6BD4500B9B11A /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 590C4D0D1FA6BD4500B9B11A /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 590C4D141FA6BD4500B9B11A; 40 | remoteInfo = saveCover; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 590C4D151FA6BD4500B9B11A /* saveCover.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = saveCover.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 590C4D181FA6BD4500B9B11A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 47 | 590C4D191FA6BD4500B9B11A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 48 | 590C4D1B1FA6BD4500B9B11A /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 49 | 590C4D1C1FA6BD4500B9B11A /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 50 | 590C4D1F1FA6BD4500B9B11A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 590C4D211FA6BD4500B9B11A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 590C4D241FA6BD4500B9B11A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53 | 590C4D261FA6BD4500B9B11A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 590C4D271FA6BD4500B9B11A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 55 | 590C4D2D1FA6BD4500B9B11A /* saveCoverTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = saveCoverTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 590C4D311FA6BD4500B9B11A /* saveCoverTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = saveCoverTests.m; sourceTree = ""; }; 57 | 590C4D331FA6BD4500B9B11A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 590C4D381FA6BD4500B9B11A /* saveCoverUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = saveCoverUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 590C4D3C1FA6BD4500B9B11A /* saveCoverUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = saveCoverUITests.m; sourceTree = ""; }; 60 | 590C4D3E1FA6BD4500B9B11A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 590C4D4E1FA6C7F400B9B11A /* CZHTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CZHTool.h; sourceTree = ""; }; 62 | 590C4D4F1FA6C7F400B9B11A /* CZHTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CZHTool.m; sourceTree = ""; }; 63 | 590C4D511FA6C94C00B9B11A /* Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Header.h; sourceTree = ""; }; 64 | 590C4D531FA6CA4400B9B11A /* CZHChooseCoverController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CZHChooseCoverController.h; sourceTree = ""; }; 65 | 590C4D541FA6CA4400B9B11A /* CZHChooseCoverController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CZHChooseCoverController.m; sourceTree = ""; }; 66 | 590C4D571FA6CBEA00B9B11A /* MBProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBProgressHUD.h; sourceTree = ""; }; 67 | 590C4D581FA6CBEA00B9B11A /* MBProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBProgressHUD.m; sourceTree = ""; }; 68 | 590C4D5A1FA6CCE500B9B11A /* UILabel+Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UILabel+Extension.h"; sourceTree = ""; }; 69 | 590C4D5B1FA6CCE500B9B11A /* UILabel+Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UILabel+Extension.m"; sourceTree = ""; }; 70 | 590C4D5D1FA6CDFA00B9B11A /* CZHChooseCoverCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CZHChooseCoverCell.h; sourceTree = ""; }; 71 | 590C4D5E1FA6CDFA00B9B11A /* CZHChooseCoverCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CZHChooseCoverCell.m; sourceTree = ""; }; 72 | 590C4D601FA6CE9D00B9B11A /* UIButton+Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIButton+Extension.h"; sourceTree = ""; }; 73 | 590C4D611FA6CE9D00B9B11A /* UIButton+Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIButton+Extension.m"; sourceTree = ""; }; 74 | 59DC4C8A1FDE898B00914721 /* haha.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = haha.mp4; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 590C4D121FA6BD4500B9B11A /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | 590C4D2A1FA6BD4500B9B11A /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | 590C4D351FA6BD4500B9B11A /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | /* End PBXFrameworksBuildPhase section */ 100 | 101 | /* Begin PBXGroup section */ 102 | 590C4D0C1FA6BD4500B9B11A = { 103 | isa = PBXGroup; 104 | children = ( 105 | 590C4D171FA6BD4500B9B11A /* saveCover */, 106 | 590C4D301FA6BD4500B9B11A /* saveCoverTests */, 107 | 590C4D3B1FA6BD4500B9B11A /* saveCoverUITests */, 108 | 590C4D161FA6BD4500B9B11A /* Products */, 109 | ); 110 | sourceTree = ""; 111 | }; 112 | 590C4D161FA6BD4500B9B11A /* Products */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 590C4D151FA6BD4500B9B11A /* saveCover.app */, 116 | 590C4D2D1FA6BD4500B9B11A /* saveCoverTests.xctest */, 117 | 590C4D381FA6BD4500B9B11A /* saveCoverUITests.xctest */, 118 | ); 119 | name = Products; 120 | sourceTree = ""; 121 | }; 122 | 590C4D171FA6BD4500B9B11A /* saveCover */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 59DC4C891FDE898B00914721 /* video */, 126 | 590C4D561FA6CBEA00B9B11A /* MBProgressHUD */, 127 | 590C4D521FA6C9F500B9B11A /* CZHChooseCover */, 128 | 590C4D181FA6BD4500B9B11A /* AppDelegate.h */, 129 | 590C4D191FA6BD4500B9B11A /* AppDelegate.m */, 130 | 590C4D1B1FA6BD4500B9B11A /* ViewController.h */, 131 | 590C4D1C1FA6BD4500B9B11A /* ViewController.m */, 132 | 590C4D1E1FA6BD4500B9B11A /* Main.storyboard */, 133 | 590C4D211FA6BD4500B9B11A /* Assets.xcassets */, 134 | 590C4D231FA6BD4500B9B11A /* LaunchScreen.storyboard */, 135 | 590C4D261FA6BD4500B9B11A /* Info.plist */, 136 | 590C4D271FA6BD4500B9B11A /* main.m */, 137 | ); 138 | path = saveCover; 139 | sourceTree = ""; 140 | }; 141 | 590C4D301FA6BD4500B9B11A /* saveCoverTests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 590C4D311FA6BD4500B9B11A /* saveCoverTests.m */, 145 | 590C4D331FA6BD4500B9B11A /* Info.plist */, 146 | ); 147 | path = saveCoverTests; 148 | sourceTree = ""; 149 | }; 150 | 590C4D3B1FA6BD4500B9B11A /* saveCoverUITests */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 590C4D3C1FA6BD4500B9B11A /* saveCoverUITests.m */, 154 | 590C4D3E1FA6BD4500B9B11A /* Info.plist */, 155 | ); 156 | path = saveCoverUITests; 157 | sourceTree = ""; 158 | }; 159 | 590C4D521FA6C9F500B9B11A /* CZHChooseCover */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 590C4D531FA6CA4400B9B11A /* CZHChooseCoverController.h */, 163 | 590C4D541FA6CA4400B9B11A /* CZHChooseCoverController.m */, 164 | 590C4D5D1FA6CDFA00B9B11A /* CZHChooseCoverCell.h */, 165 | 590C4D5E1FA6CDFA00B9B11A /* CZHChooseCoverCell.m */, 166 | 590C4D5A1FA6CCE500B9B11A /* UILabel+Extension.h */, 167 | 590C4D5B1FA6CCE500B9B11A /* UILabel+Extension.m */, 168 | 590C4D601FA6CE9D00B9B11A /* UIButton+Extension.h */, 169 | 590C4D611FA6CE9D00B9B11A /* UIButton+Extension.m */, 170 | 590C4D4E1FA6C7F400B9B11A /* CZHTool.h */, 171 | 590C4D4F1FA6C7F400B9B11A /* CZHTool.m */, 172 | 590C4D511FA6C94C00B9B11A /* Header.h */, 173 | ); 174 | path = CZHChooseCover; 175 | sourceTree = ""; 176 | }; 177 | 590C4D561FA6CBEA00B9B11A /* MBProgressHUD */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 590C4D571FA6CBEA00B9B11A /* MBProgressHUD.h */, 181 | 590C4D581FA6CBEA00B9B11A /* MBProgressHUD.m */, 182 | ); 183 | path = MBProgressHUD; 184 | sourceTree = ""; 185 | }; 186 | 59DC4C891FDE898B00914721 /* video */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 59DC4C8A1FDE898B00914721 /* haha.mp4 */, 190 | ); 191 | path = video; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXGroup section */ 195 | 196 | /* Begin PBXNativeTarget section */ 197 | 590C4D141FA6BD4500B9B11A /* saveCover */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = 590C4D411FA6BD4500B9B11A /* Build configuration list for PBXNativeTarget "saveCover" */; 200 | buildPhases = ( 201 | 590C4D111FA6BD4500B9B11A /* Sources */, 202 | 590C4D121FA6BD4500B9B11A /* Frameworks */, 203 | 590C4D131FA6BD4500B9B11A /* Resources */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | ); 209 | name = saveCover; 210 | productName = saveCover; 211 | productReference = 590C4D151FA6BD4500B9B11A /* saveCover.app */; 212 | productType = "com.apple.product-type.application"; 213 | }; 214 | 590C4D2C1FA6BD4500B9B11A /* saveCoverTests */ = { 215 | isa = PBXNativeTarget; 216 | buildConfigurationList = 590C4D441FA6BD4500B9B11A /* Build configuration list for PBXNativeTarget "saveCoverTests" */; 217 | buildPhases = ( 218 | 590C4D291FA6BD4500B9B11A /* Sources */, 219 | 590C4D2A1FA6BD4500B9B11A /* Frameworks */, 220 | 590C4D2B1FA6BD4500B9B11A /* Resources */, 221 | ); 222 | buildRules = ( 223 | ); 224 | dependencies = ( 225 | 590C4D2F1FA6BD4500B9B11A /* PBXTargetDependency */, 226 | ); 227 | name = saveCoverTests; 228 | productName = saveCoverTests; 229 | productReference = 590C4D2D1FA6BD4500B9B11A /* saveCoverTests.xctest */; 230 | productType = "com.apple.product-type.bundle.unit-test"; 231 | }; 232 | 590C4D371FA6BD4500B9B11A /* saveCoverUITests */ = { 233 | isa = PBXNativeTarget; 234 | buildConfigurationList = 590C4D471FA6BD4500B9B11A /* Build configuration list for PBXNativeTarget "saveCoverUITests" */; 235 | buildPhases = ( 236 | 590C4D341FA6BD4500B9B11A /* Sources */, 237 | 590C4D351FA6BD4500B9B11A /* Frameworks */, 238 | 590C4D361FA6BD4500B9B11A /* Resources */, 239 | ); 240 | buildRules = ( 241 | ); 242 | dependencies = ( 243 | 590C4D3A1FA6BD4500B9B11A /* PBXTargetDependency */, 244 | ); 245 | name = saveCoverUITests; 246 | productName = saveCoverUITests; 247 | productReference = 590C4D381FA6BD4500B9B11A /* saveCoverUITests.xctest */; 248 | productType = "com.apple.product-type.bundle.ui-testing"; 249 | }; 250 | /* End PBXNativeTarget section */ 251 | 252 | /* Begin PBXProject section */ 253 | 590C4D0D1FA6BD4500B9B11A /* Project object */ = { 254 | isa = PBXProject; 255 | attributes = { 256 | CLASSPREFIX = CZH; 257 | LastUpgradeCheck = 0910; 258 | ORGANIZATIONNAME = "程召华"; 259 | TargetAttributes = { 260 | 590C4D141FA6BD4500B9B11A = { 261 | CreatedOnToolsVersion = 9.1; 262 | ProvisioningStyle = Automatic; 263 | }; 264 | 590C4D2C1FA6BD4500B9B11A = { 265 | CreatedOnToolsVersion = 9.1; 266 | ProvisioningStyle = Automatic; 267 | TestTargetID = 590C4D141FA6BD4500B9B11A; 268 | }; 269 | 590C4D371FA6BD4500B9B11A = { 270 | CreatedOnToolsVersion = 9.1; 271 | ProvisioningStyle = Automatic; 272 | TestTargetID = 590C4D141FA6BD4500B9B11A; 273 | }; 274 | }; 275 | }; 276 | buildConfigurationList = 590C4D101FA6BD4500B9B11A /* Build configuration list for PBXProject "saveCover" */; 277 | compatibilityVersion = "Xcode 8.0"; 278 | developmentRegion = en; 279 | hasScannedForEncodings = 0; 280 | knownRegions = ( 281 | en, 282 | Base, 283 | ); 284 | mainGroup = 590C4D0C1FA6BD4500B9B11A; 285 | productRefGroup = 590C4D161FA6BD4500B9B11A /* Products */; 286 | projectDirPath = ""; 287 | projectRoot = ""; 288 | targets = ( 289 | 590C4D141FA6BD4500B9B11A /* saveCover */, 290 | 590C4D2C1FA6BD4500B9B11A /* saveCoverTests */, 291 | 590C4D371FA6BD4500B9B11A /* saveCoverUITests */, 292 | ); 293 | }; 294 | /* End PBXProject section */ 295 | 296 | /* Begin PBXResourcesBuildPhase section */ 297 | 590C4D131FA6BD4500B9B11A /* Resources */ = { 298 | isa = PBXResourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 590C4D251FA6BD4500B9B11A /* LaunchScreen.storyboard in Resources */, 302 | 59DC4C8B1FDE898B00914721 /* haha.mp4 in Resources */, 303 | 590C4D221FA6BD4500B9B11A /* Assets.xcassets in Resources */, 304 | 590C4D201FA6BD4500B9B11A /* Main.storyboard in Resources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | 590C4D2B1FA6BD4500B9B11A /* Resources */ = { 309 | isa = PBXResourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | 590C4D361FA6BD4500B9B11A /* Resources */ = { 316 | isa = PBXResourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | ); 320 | runOnlyForDeploymentPostprocessing = 0; 321 | }; 322 | /* End PBXResourcesBuildPhase section */ 323 | 324 | /* Begin PBXSourcesBuildPhase section */ 325 | 590C4D111FA6BD4500B9B11A /* Sources */ = { 326 | isa = PBXSourcesBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | 590C4D551FA6CA4400B9B11A /* CZHChooseCoverController.m in Sources */, 330 | 590C4D591FA6CBEA00B9B11A /* MBProgressHUD.m in Sources */, 331 | 590C4D5C1FA6CCE500B9B11A /* UILabel+Extension.m in Sources */, 332 | 590C4D1D1FA6BD4500B9B11A /* ViewController.m in Sources */, 333 | 590C4D5F1FA6CDFA00B9B11A /* CZHChooseCoverCell.m in Sources */, 334 | 590C4D281FA6BD4500B9B11A /* main.m in Sources */, 335 | 590C4D1A1FA6BD4500B9B11A /* AppDelegate.m in Sources */, 336 | 590C4D621FA6CE9D00B9B11A /* UIButton+Extension.m in Sources */, 337 | 590C4D501FA6C7F400B9B11A /* CZHTool.m in Sources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | 590C4D291FA6BD4500B9B11A /* Sources */ = { 342 | isa = PBXSourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | 590C4D321FA6BD4500B9B11A /* saveCoverTests.m in Sources */, 346 | ); 347 | runOnlyForDeploymentPostprocessing = 0; 348 | }; 349 | 590C4D341FA6BD4500B9B11A /* Sources */ = { 350 | isa = PBXSourcesBuildPhase; 351 | buildActionMask = 2147483647; 352 | files = ( 353 | 590C4D3D1FA6BD4500B9B11A /* saveCoverUITests.m in Sources */, 354 | ); 355 | runOnlyForDeploymentPostprocessing = 0; 356 | }; 357 | /* End PBXSourcesBuildPhase section */ 358 | 359 | /* Begin PBXTargetDependency section */ 360 | 590C4D2F1FA6BD4500B9B11A /* PBXTargetDependency */ = { 361 | isa = PBXTargetDependency; 362 | target = 590C4D141FA6BD4500B9B11A /* saveCover */; 363 | targetProxy = 590C4D2E1FA6BD4500B9B11A /* PBXContainerItemProxy */; 364 | }; 365 | 590C4D3A1FA6BD4500B9B11A /* PBXTargetDependency */ = { 366 | isa = PBXTargetDependency; 367 | target = 590C4D141FA6BD4500B9B11A /* saveCover */; 368 | targetProxy = 590C4D391FA6BD4500B9B11A /* PBXContainerItemProxy */; 369 | }; 370 | /* End PBXTargetDependency section */ 371 | 372 | /* Begin PBXVariantGroup section */ 373 | 590C4D1E1FA6BD4500B9B11A /* Main.storyboard */ = { 374 | isa = PBXVariantGroup; 375 | children = ( 376 | 590C4D1F1FA6BD4500B9B11A /* Base */, 377 | ); 378 | name = Main.storyboard; 379 | sourceTree = ""; 380 | }; 381 | 590C4D231FA6BD4500B9B11A /* LaunchScreen.storyboard */ = { 382 | isa = PBXVariantGroup; 383 | children = ( 384 | 590C4D241FA6BD4500B9B11A /* Base */, 385 | ); 386 | name = LaunchScreen.storyboard; 387 | sourceTree = ""; 388 | }; 389 | /* End PBXVariantGroup section */ 390 | 391 | /* Begin XCBuildConfiguration section */ 392 | 590C4D3F1FA6BD4500B9B11A /* Debug */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ALWAYS_SEARCH_USER_PATHS = NO; 396 | CLANG_ANALYZER_NONNULL = YES; 397 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 398 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 399 | CLANG_CXX_LIBRARY = "libc++"; 400 | CLANG_ENABLE_MODULES = YES; 401 | CLANG_ENABLE_OBJC_ARC = YES; 402 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 403 | CLANG_WARN_BOOL_CONVERSION = YES; 404 | CLANG_WARN_COMMA = YES; 405 | CLANG_WARN_CONSTANT_CONVERSION = YES; 406 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 407 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INFINITE_RECURSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 414 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 415 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 416 | CLANG_WARN_STRICT_PROTOTYPES = YES; 417 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 418 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 419 | CLANG_WARN_UNREACHABLE_CODE = YES; 420 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 421 | CODE_SIGN_IDENTITY = "iPhone Developer"; 422 | COPY_PHASE_STRIP = NO; 423 | DEBUG_INFORMATION_FORMAT = dwarf; 424 | ENABLE_STRICT_OBJC_MSGSEND = YES; 425 | ENABLE_TESTABILITY = YES; 426 | GCC_C_LANGUAGE_STANDARD = gnu11; 427 | GCC_DYNAMIC_NO_PIC = NO; 428 | GCC_NO_COMMON_BLOCKS = YES; 429 | GCC_OPTIMIZATION_LEVEL = 0; 430 | GCC_PREPROCESSOR_DEFINITIONS = ( 431 | "DEBUG=1", 432 | "$(inherited)", 433 | ); 434 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 435 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 436 | GCC_WARN_UNDECLARED_SELECTOR = YES; 437 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 438 | GCC_WARN_UNUSED_FUNCTION = YES; 439 | GCC_WARN_UNUSED_VARIABLE = YES; 440 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 441 | MTL_ENABLE_DEBUG_INFO = YES; 442 | ONLY_ACTIVE_ARCH = YES; 443 | SDKROOT = iphoneos; 444 | }; 445 | name = Debug; 446 | }; 447 | 590C4D401FA6BD4500B9B11A /* Release */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ALWAYS_SEARCH_USER_PATHS = NO; 451 | CLANG_ANALYZER_NONNULL = YES; 452 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 453 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 454 | CLANG_CXX_LIBRARY = "libc++"; 455 | CLANG_ENABLE_MODULES = YES; 456 | CLANG_ENABLE_OBJC_ARC = YES; 457 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 458 | CLANG_WARN_BOOL_CONVERSION = YES; 459 | CLANG_WARN_COMMA = YES; 460 | CLANG_WARN_CONSTANT_CONVERSION = YES; 461 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 462 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 463 | CLANG_WARN_EMPTY_BODY = YES; 464 | CLANG_WARN_ENUM_CONVERSION = YES; 465 | CLANG_WARN_INFINITE_RECURSION = YES; 466 | CLANG_WARN_INT_CONVERSION = YES; 467 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 468 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 469 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 470 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 471 | CLANG_WARN_STRICT_PROTOTYPES = YES; 472 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 473 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 474 | CLANG_WARN_UNREACHABLE_CODE = YES; 475 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 476 | CODE_SIGN_IDENTITY = "iPhone Developer"; 477 | COPY_PHASE_STRIP = NO; 478 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 479 | ENABLE_NS_ASSERTIONS = NO; 480 | ENABLE_STRICT_OBJC_MSGSEND = YES; 481 | GCC_C_LANGUAGE_STANDARD = gnu11; 482 | GCC_NO_COMMON_BLOCKS = YES; 483 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 484 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 485 | GCC_WARN_UNDECLARED_SELECTOR = YES; 486 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 487 | GCC_WARN_UNUSED_FUNCTION = YES; 488 | GCC_WARN_UNUSED_VARIABLE = YES; 489 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 490 | MTL_ENABLE_DEBUG_INFO = NO; 491 | SDKROOT = iphoneos; 492 | VALIDATE_PRODUCT = YES; 493 | }; 494 | name = Release; 495 | }; 496 | 590C4D421FA6BD4500B9B11A /* Debug */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 500 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 501 | CODE_SIGN_STYLE = Automatic; 502 | DEVELOPMENT_TEAM = HY6G4W6G55; 503 | INFOPLIST_FILE = saveCover/Info.plist; 504 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 505 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 506 | PRODUCT_BUNDLE_IDENTIFIER = baiyoushang.saveCover; 507 | PRODUCT_NAME = "$(TARGET_NAME)"; 508 | PROVISIONING_PROFILE_SPECIFIER = ""; 509 | TARGETED_DEVICE_FAMILY = "1,2"; 510 | }; 511 | name = Debug; 512 | }; 513 | 590C4D431FA6BD4500B9B11A /* Release */ = { 514 | isa = XCBuildConfiguration; 515 | buildSettings = { 516 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 517 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 518 | CODE_SIGN_STYLE = Automatic; 519 | DEVELOPMENT_TEAM = HY6G4W6G55; 520 | INFOPLIST_FILE = saveCover/Info.plist; 521 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 522 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 523 | PRODUCT_BUNDLE_IDENTIFIER = baiyoushang.saveCover; 524 | PRODUCT_NAME = "$(TARGET_NAME)"; 525 | PROVISIONING_PROFILE_SPECIFIER = ""; 526 | TARGETED_DEVICE_FAMILY = "1,2"; 527 | }; 528 | name = Release; 529 | }; 530 | 590C4D451FA6BD4500B9B11A /* Debug */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | BUNDLE_LOADER = "$(TEST_HOST)"; 534 | CODE_SIGN_STYLE = Automatic; 535 | INFOPLIST_FILE = saveCoverTests/Info.plist; 536 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 537 | PRODUCT_BUNDLE_IDENTIFIER = baiyoushang.saveCoverTests; 538 | PRODUCT_NAME = "$(TARGET_NAME)"; 539 | TARGETED_DEVICE_FAMILY = "1,2"; 540 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/saveCover.app/saveCover"; 541 | }; 542 | name = Debug; 543 | }; 544 | 590C4D461FA6BD4500B9B11A /* Release */ = { 545 | isa = XCBuildConfiguration; 546 | buildSettings = { 547 | BUNDLE_LOADER = "$(TEST_HOST)"; 548 | CODE_SIGN_STYLE = Automatic; 549 | INFOPLIST_FILE = saveCoverTests/Info.plist; 550 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 551 | PRODUCT_BUNDLE_IDENTIFIER = baiyoushang.saveCoverTests; 552 | PRODUCT_NAME = "$(TARGET_NAME)"; 553 | TARGETED_DEVICE_FAMILY = "1,2"; 554 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/saveCover.app/saveCover"; 555 | }; 556 | name = Release; 557 | }; 558 | 590C4D481FA6BD4500B9B11A /* Debug */ = { 559 | isa = XCBuildConfiguration; 560 | buildSettings = { 561 | CODE_SIGN_STYLE = Automatic; 562 | INFOPLIST_FILE = saveCoverUITests/Info.plist; 563 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 564 | PRODUCT_BUNDLE_IDENTIFIER = baiyoushang.saveCoverUITests; 565 | PRODUCT_NAME = "$(TARGET_NAME)"; 566 | TARGETED_DEVICE_FAMILY = "1,2"; 567 | TEST_TARGET_NAME = saveCover; 568 | }; 569 | name = Debug; 570 | }; 571 | 590C4D491FA6BD4500B9B11A /* Release */ = { 572 | isa = XCBuildConfiguration; 573 | buildSettings = { 574 | CODE_SIGN_STYLE = Automatic; 575 | INFOPLIST_FILE = saveCoverUITests/Info.plist; 576 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 577 | PRODUCT_BUNDLE_IDENTIFIER = baiyoushang.saveCoverUITests; 578 | PRODUCT_NAME = "$(TARGET_NAME)"; 579 | TARGETED_DEVICE_FAMILY = "1,2"; 580 | TEST_TARGET_NAME = saveCover; 581 | }; 582 | name = Release; 583 | }; 584 | /* End XCBuildConfiguration section */ 585 | 586 | /* Begin XCConfigurationList section */ 587 | 590C4D101FA6BD4500B9B11A /* Build configuration list for PBXProject "saveCover" */ = { 588 | isa = XCConfigurationList; 589 | buildConfigurations = ( 590 | 590C4D3F1FA6BD4500B9B11A /* Debug */, 591 | 590C4D401FA6BD4500B9B11A /* Release */, 592 | ); 593 | defaultConfigurationIsVisible = 0; 594 | defaultConfigurationName = Release; 595 | }; 596 | 590C4D411FA6BD4500B9B11A /* Build configuration list for PBXNativeTarget "saveCover" */ = { 597 | isa = XCConfigurationList; 598 | buildConfigurations = ( 599 | 590C4D421FA6BD4500B9B11A /* Debug */, 600 | 590C4D431FA6BD4500B9B11A /* Release */, 601 | ); 602 | defaultConfigurationIsVisible = 0; 603 | defaultConfigurationName = Release; 604 | }; 605 | 590C4D441FA6BD4500B9B11A /* Build configuration list for PBXNativeTarget "saveCoverTests" */ = { 606 | isa = XCConfigurationList; 607 | buildConfigurations = ( 608 | 590C4D451FA6BD4500B9B11A /* Debug */, 609 | 590C4D461FA6BD4500B9B11A /* Release */, 610 | ); 611 | defaultConfigurationIsVisible = 0; 612 | defaultConfigurationName = Release; 613 | }; 614 | 590C4D471FA6BD4500B9B11A /* Build configuration list for PBXNativeTarget "saveCoverUITests" */ = { 615 | isa = XCConfigurationList; 616 | buildConfigurations = ( 617 | 590C4D481FA6BD4500B9B11A /* Debug */, 618 | 590C4D491FA6BD4500B9B11A /* Release */, 619 | ); 620 | defaultConfigurationIsVisible = 0; 621 | defaultConfigurationName = Release; 622 | }; 623 | /* End XCConfigurationList section */ 624 | }; 625 | rootObject = 590C4D0D1FA6BD4500B9B11A /* Project object */; 626 | } 627 | -------------------------------------------------------------------------------- /saveCover.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /saveCover.xcodeproj/project.xcworkspace/xcuserdata/chengzhaohua.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolincheng/CZHChooseCoverController/e2771dadc64893e022d9ce18e7e0b7b651d57703/saveCover.xcodeproj/project.xcworkspace/xcuserdata/chengzhaohua.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /saveCover.xcodeproj/xcuserdata/chengzhaohua.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /saveCover.xcodeproj/xcuserdata/chengzhaohua.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | saveCover.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /saveCover/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /saveCover/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_back@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "icon_back@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/back.imageset/icon_back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolincheng/CZHChooseCoverController/e2771dadc64893e022d9ce18e7e0b7b651d57703/saveCover/Assets.xcassets/back.imageset/icon_back@2x.png -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/back.imageset/icon_back@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolincheng/CZHChooseCoverController/e2771dadc64893e022d9ce18e7e0b7b651d57703/saveCover/Assets.xcassets/back.imageset/icon_back@3x.png -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/slider_deselect.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "btn_n_cover@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "btn_n_cover@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/slider_deselect.imageset/btn_n_cover@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolincheng/CZHChooseCoverController/e2771dadc64893e022d9ce18e7e0b7b651d57703/saveCover/Assets.xcassets/slider_deselect.imageset/btn_n_cover@2x.png -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/slider_deselect.imageset/btn_n_cover@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolincheng/CZHChooseCoverController/e2771dadc64893e022d9ce18e7e0b7b651d57703/saveCover/Assets.xcassets/slider_deselect.imageset/btn_n_cover@3x.png -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/slider_select.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "btn_p_cover@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "btn_p_cover@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/slider_select.imageset/btn_p_cover@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolincheng/CZHChooseCoverController/e2771dadc64893e022d9ce18e7e0b7b651d57703/saveCover/Assets.xcassets/slider_select.imageset/btn_p_cover@2x.png -------------------------------------------------------------------------------- /saveCover/Assets.xcassets/slider_select.imageset/btn_p_cover@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolincheng/CZHChooseCoverController/e2771dadc64893e022d9ce18e7e0b7b651d57703/saveCover/Assets.xcassets/slider_select.imageset/btn_p_cover@3x.png -------------------------------------------------------------------------------- /saveCover/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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /saveCover/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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/CZHChooseCoverCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // CZHChooseCoverCell.h 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CZHChooseCoverCell : UICollectionViewCell 12 | ///<#注释#> 13 | @property (nonatomic, strong) UIImage *coverImage; 14 | @end 15 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/CZHChooseCoverCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // CZHChooseCoverCell.m 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import "CZHChooseCoverCell.h" 10 | 11 | 12 | @interface CZHChooseCoverCell () 13 | ///<#注释#> 14 | @property (nonatomic, weak) UIImageView *imageView; 15 | @end 16 | 17 | @implementation CZHChooseCoverCell 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame { 20 | if (self = [super initWithFrame:frame]) { 21 | 22 | [self setView]; 23 | 24 | } 25 | return self; 26 | } 27 | 28 | - (void)setView { 29 | 30 | UIImageView *imageView = [[UIImageView alloc] init]; 31 | imageView.frame = self.bounds; 32 | imageView.contentMode = UIViewContentModeScaleAspectFill; 33 | imageView.clipsToBounds = YES; 34 | [self.contentView addSubview:imageView]; 35 | self.imageView = imageView; 36 | 37 | } 38 | 39 | - (void)setCoverImage:(UIImage *)coverImage { 40 | _coverImage = coverImage; 41 | self.imageView.image = coverImage; 42 | } 43 | 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/CZHChooseCoverController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CZHChooseCoverController.h 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | 12 | @interface CZHChooseCoverController : UIViewController 13 | ///本地视频路径 14 | @property (nonatomic, copy) NSURL *videoPath; 15 | ///封面回调 16 | @property (nonatomic, copy) void (^coverImageBlock)(UIImage *coverImage); 17 | @end 18 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/CZHChooseCoverController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CZHChooseCoverController.m 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | //底部显示的个数 9 | #define PHOTP_COUNT 6 10 | 11 | #import "CZHChooseCoverController.h" 12 | #import 13 | #import "Header.h" 14 | #import "CZHChooseCoverCell.h" 15 | 16 | typedef NS_ENUM(NSInteger, CZHChooseCoverControllerButtonType) { 17 | //返回 18 | CZHChooseCoverControllerButtonTypeBack, 19 | //完成 20 | CZHChooseCoverControllerButtonTypeComplete 21 | }; 22 | static NSString *const ID = @"CZHChooseCoverCell"; 23 | @interface CZHChooseCoverController () 24 | /// 25 | @property (nonatomic, weak) UICollectionView *collectionView; 26 | ///图片显示 27 | @property (nonatomic, weak) UIImageView *imageView; 28 | ///总帧数 29 | @property (nonatomic, assign) CMTimeValue timeValue; 30 | ///比例 31 | @property (nonatomic, assign) CMTimeScale timeScale; 32 | ///照片数组 33 | @property (nonatomic, strong) NSMutableArray *photoArrays; 34 | @end 35 | 36 | @implementation CZHChooseCoverController 37 | 38 | - (NSMutableArray *)photoArrays { 39 | if (!_photoArrays) { 40 | _photoArrays = [NSMutableArray array]; 41 | } 42 | return _photoArrays; 43 | } 44 | 45 | - (void)viewDidLoad { 46 | [super viewDidLoad]; 47 | self.view.backgroundColor = CZHColor(0x000000); 48 | 49 | [self getVideoTotalValueAndScale]; 50 | 51 | [self setNavigationBar]; 52 | 53 | [self setUpView]; 54 | } 55 | 56 | - (void)getVideoTotalValueAndScale { 57 | 58 | AVURLAsset * asset = [AVURLAsset assetWithURL:self.videoPath]; 59 | CMTime time = [asset duration]; 60 | self.timeValue = time.value; 61 | self.timeScale = time.timescale; 62 | 63 | if (time.value < 1) { 64 | 65 | [self dismissViewControllerAnimated:YES completion:nil]; 66 | return; 67 | } 68 | 69 | NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]; 70 | AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:self.videoPath options:opts]; 71 | AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset]; 72 | generator.appliesPreferredTrackTransform = YES; 73 | 74 | long long baseCount = time.value / PHOTP_COUNT; 75 | //取出PHOTP_COUNT张图片,存放到数组,用于collectionview 76 | for (NSInteger i = 0 ; i < PHOTP_COUNT; i++) { 77 | 78 | NSError *error = nil; 79 | CGImageRef img = [generator copyCGImageAtTime:CMTimeMake(i * baseCount, time.timescale) actualTime:NULL error:&error]; 80 | { 81 | UIImage *image = [UIImage imageWithCGImage:img]; 82 | 83 | [self.photoArrays addObject:image]; 84 | } 85 | ///释放内存 86 | CGImageRelease(img); 87 | } 88 | 89 | } 90 | 91 | 92 | 93 | - (void)setUpView { 94 | 95 | UIImageView *imageView = [[UIImageView alloc] init]; 96 | imageView.frame = CGRectMake(0, CZH_ScaleHeight(65), ScreenWidth, ScreenWidth); 97 | imageView.contentMode = UIViewContentModeScaleAspectFill; 98 | imageView.clipsToBounds = YES; 99 | imageView.backgroundColor = [UIColor clearColor]; 100 | [self.view addSubview:imageView]; 101 | self.imageView = imageView; 102 | 103 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 104 | layout.itemSize = CGSizeMake(ScreenWidth/PHOTP_COUNT, ScreenWidth/PHOTP_COUNT); 105 | layout.minimumInteritemSpacing = 0; 106 | 107 | CGRect collectionViewF = CGRectMake(0, CZH_ScaleHeight(461), ScreenWidth, CZH_ScaleHeight(62.5)); 108 | UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:collectionViewF collectionViewLayout:layout]; 109 | collectionView.dataSource = self; 110 | collectionView.backgroundColor = [UIColor clearColor]; 111 | [collectionView registerClass:[CZHChooseCoverCell class] forCellWithReuseIdentifier:ID]; 112 | [self.view addSubview:collectionView]; 113 | self.collectionView = collectionView; 114 | 115 | 116 | 117 | UIImage *selected = [UIImage imageNamed:@"slider_select"]; 118 | UIImage *deselected = [UIImage imageNamed:@"slider_deselect"]; 119 | 120 | UISlider *slider = [[UISlider alloc] init]; 121 | slider.frame = CGRectMake(0, CZH_ScaleHeight(461), ScreenWidth, CZH_ScaleHeight(62.5)); 122 | 123 | [slider setThumbImage:deselected forState:UIControlStateNormal]; 124 | [slider setThumbImage:selected forState:UIControlStateHighlighted]; 125 | //透明的图片 126 | UIImage *image = [CZHTool imageWithColor:[UIColor clearColor] size:CGSizeMake(1, 1)]; 127 | 128 | [slider setMinimumTrackImage:image forState:UIControlStateNormal]; 129 | [slider setMaximumTrackImage:image forState:UIControlStateNormal]; 130 | 131 | slider.maximumValue = self.timeValue; 132 | slider.minimumValue = 0; 133 | [slider addTarget:self action:@selector(valueChange:) forControlEvents:UIControlEventValueChanged]; 134 | [self.view addSubview:slider]; 135 | 136 | //手动选取一张 137 | [self chooseWithTime:0]; 138 | 139 | 140 | 141 | CGFloat tipsLabelX = 0; 142 | CGFloat tipsLabelY = CZH_ScaleHeight(570); 143 | CGFloat tipsLabelW = ScreenWidth; 144 | CGFloat tipsLabelH = CZH_ScaleHeight(19); 145 | CGRect tipsLabelF = CGRectMake(tipsLabelX, tipsLabelY, tipsLabelW, tipsLabelH); 146 | UILabel *tipsLabel = [UILabel setLabelWithFrame:tipsLabelF font:CZHGlobelNormalFont(16) textColor:CZHColor(0xffffff) textAliment:NSTextAlignmentCenter text:@"优质的封面更能让大家喜欢哟"]; 147 | [self.view addSubview:tipsLabel]; 148 | 149 | 150 | 151 | } 152 | 153 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 154 | return self.photoArrays.count; 155 | } 156 | 157 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 158 | 159 | CZHChooseCoverCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath]; 160 | cell.coverImage = self.photoArrays[indexPath.item]; 161 | return cell; 162 | } 163 | 164 | 165 | - (void)valueChange:(UISlider *)sender { 166 | 167 | CMTimeValue timeValue = sender.value; 168 | 169 | [self chooseWithTime:timeValue]; 170 | } 171 | 172 | 173 | - (void)chooseWithTime:(CMTimeValue)time { 174 | 175 | 176 | NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]; 177 | AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:self.videoPath options:opts]; 178 | AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset]; 179 | generator.appliesPreferredTrackTransform = YES; 180 | 181 | /**The actual time of the generated images will be within the range [requestedTime-toleranceBefore, requestedTime+toleranceAfter] and may differ from the requested time for efficiency. 182 | Pass kCMTimeZero for both toleranceBefore and toleranceAfter to request frame-accurate image generation; this may incur additional decoding delay. 183 | Default is kCMTimePositiveInfinity.*/ 184 | 185 | generator.requestedTimeToleranceAfter = kCMTimeZero; 186 | generator.requestedTimeToleranceBefore = kCMTimeZero; 187 | 188 | NSError *error = nil; 189 | CGImageRef img = [generator copyCGImageAtTime:CMTimeMake(time, self.timeScale) actualTime:NULL error:&error]; 190 | { 191 | UIImage *image = [UIImage imageWithCGImage:img]; 192 | 193 | self.imageView.image = image; 194 | } 195 | ///释放内存 196 | CGImageRelease(img); 197 | } 198 | 199 | 200 | - (UIImage *)convertViewToImage 201 | { 202 | UIGraphicsBeginImageContext(self.collectionView.bounds.size); 203 | [self.collectionView drawViewHierarchyInRect:self.collectionView.bounds afterScreenUpdates:YES]; 204 | UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext(); 205 | UIGraphicsEndImageContext(); 206 | 207 | return screenshot; 208 | } 209 | 210 | 211 | - (void)setNavigationBar { 212 | 213 | CGFloat titleLabelX = 0; 214 | CGFloat titleLabelY = 20; 215 | CGFloat titleLabelW = ScreenWidth; 216 | CGFloat titleLabelH = 44; 217 | CGRect titleLabelF = CGRectMake(titleLabelX, titleLabelY, titleLabelW, titleLabelH); 218 | UILabel *titleLabel = [UILabel setLabelWithFrame:titleLabelF font:CZHGlobelNormalFont(17) textColor:CZHColor(0xffffff) textAliment:NSTextAlignmentCenter text:@"设置封面"]; 219 | [self.view addSubview:titleLabel]; 220 | 221 | CGFloat backButtonX = CZH_ScaleWidth(7); 222 | CGFloat backButtonW = CZH_ScaleHeight(30); 223 | CGFloat backButtonH = CZH_ScaleHeight(30); 224 | CGFloat backButtonY = (44 - backButtonH) * 0.5 + 20; 225 | CGRect backButtonF = CGRectMake(backButtonX, backButtonY, backButtonW, backButtonH); 226 | UIButton *backButton = [UIButton czh_buttonWithTarget:self action:@selector(buttonClick:) frame:backButtonF imageName:@"back"]; 227 | backButton.tag = CZHChooseCoverControllerButtonTypeBack; 228 | [self.view addSubview:backButton]; 229 | 230 | 231 | 232 | CGFloat completeButtonW = CZH_ScaleHeight(55); 233 | CGFloat completeButtonH = 44; 234 | CGFloat completeButtonY = 20; 235 | CGFloat completeButtonX = ScreenWidth - completeButtonW; 236 | CGRect completeButtonF = CGRectMake(completeButtonX, completeButtonY, completeButtonW, completeButtonH); 237 | UIButton *completeButton = [UIButton czh_buttonWithTarget:self action:@selector(buttonClick:) frame:completeButtonF titleColor:CZHColor(0xffffff) titleFont:CZHGlobelNormalFont(17) title:@"完成"]; 238 | completeButton.tag = CZHChooseCoverControllerButtonTypeComplete; 239 | [self.view addSubview:completeButton]; 240 | } 241 | 242 | 243 | 244 | - (void)buttonClick:(UIButton *)sender { 245 | if (sender.tag == CZHChooseCoverControllerButtonTypeBack) { 246 | [self dismissViewControllerAnimated:YES completion:nil]; 247 | } else if (sender.tag == CZHChooseCoverControllerButtonTypeComplete) { 248 | //封面图片回调 249 | if (_coverImageBlock) { 250 | _coverImageBlock(self.imageView.image); 251 | } 252 | [self dismissViewControllerAnimated:YES completion:nil]; 253 | } 254 | } 255 | 256 | 257 | 258 | @end 259 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/CZHTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // CZHTool.h 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CZHTool : NSObject 13 | //获取视频的大小和时间 14 | + (NSDictionary *)getLocalVideoSizeAndTimeWithSourcePath:(NSString *)path; 15 | //吧mov视频转换成mp4格式 16 | + (void)convertMovTypeIntoMp4TypeWithSourceUrl:(NSURL *)sourceUrl convertSuccess:(void (^)(NSURL *path))convertSuccess; 17 | //创建视频存放文件夹 18 | + (void)createVideoFolderIfNotExist; 19 | //颜色转换图片 20 | + (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size; 21 | @end 22 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/CZHTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // CZHTool.m 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | #define VIDEO_FOLDER @"videoFolder" 9 | #import "CZHTool.h" 10 | #import 11 | #import "MBProgressHUD.h" 12 | #import "AppDelegate.h" 13 | @implementation CZHTool 14 | 15 | + (NSDictionary *)getLocalVideoSizeAndTimeWithSourcePath:(NSString *)path{ 16 | AVURLAsset * asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:path]]; 17 | CMTime time = [asset duration]; 18 | int seconds = ceil(time.value/time.timescale); 19 | 20 | NSInteger fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize; 21 | 22 | NSMutableDictionary *dic = [NSMutableDictionary dictionary]; 23 | dic[@"size"] = @(fileSize); 24 | dic[@"duration"] = @(seconds); 25 | return dic; 26 | } 27 | 28 | + (void)convertMovTypeIntoMp4TypeWithSourceUrl:(NSURL *)sourceUrl convertSuccess:(void (^)(NSURL *path))convertSuccess { 29 | 30 | [self createVideoFolderIfNotExist]; 31 | 32 | AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:sourceUrl options:nil]; 33 | 34 | NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset]; 35 | 36 | // BWJLog(@"%@",compatiblePresets); 37 | 38 | if ([compatiblePresets containsObject:AVAssetExportPresetHighestQuality]) { 39 | 40 | AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetHighestQuality]; 41 | 42 | 43 | NSString * resultPath = [self getVideoMergeFilePathString]; 44 | 45 | NSLog(@"resultPath = %@",resultPath); 46 | 47 | 48 | exportSession.outputURL = [NSURL fileURLWithPath:resultPath]; 49 | 50 | exportSession.outputFileType = AVFileTypeMPEG4; 51 | 52 | exportSession.shouldOptimizeForNetworkUse = YES; 53 | 54 | [exportSession exportAsynchronouslyWithCompletionHandler:^{ 55 | dispatch_async(dispatch_get_main_queue(), ^{ 56 | if (exportSession.status == AVAssetExportSessionStatusCompleted) { 57 | if (convertSuccess) { 58 | convertSuccess([NSURL fileURLWithPath:resultPath]); 59 | } 60 | } else { 61 | 62 | 63 | } 64 | }); 65 | 66 | }]; 67 | } 68 | } 69 | 70 | + (NSString *)getVideoMergeFilePathString 71 | { 72 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 73 | NSString *path = [paths objectAtIndex:0]; 74 | 75 | path = [path stringByAppendingPathComponent:VIDEO_FOLDER]; 76 | 77 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 78 | formatter.dateFormat = @"yyyyMMddHHmmss"; 79 | NSString *nowTimeStr = [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:0]]; 80 | 81 | NSString *fileName = [[path stringByAppendingPathComponent:nowTimeStr] stringByAppendingString:@"merge.mp4"]; 82 | 83 | return fileName; 84 | } 85 | 86 | 87 | + (void)createVideoFolderIfNotExist 88 | { 89 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 90 | NSString *path = [paths objectAtIndex:0]; 91 | 92 | NSString *folderPath = [path stringByAppendingPathComponent:VIDEO_FOLDER]; 93 | 94 | NSFileManager *fileManager = [NSFileManager defaultManager]; 95 | BOOL isDir = NO; 96 | BOOL isDirExist = [fileManager fileExistsAtPath:folderPath isDirectory:&isDir]; 97 | 98 | if(!(isDirExist && isDir)) 99 | { 100 | BOOL bCreateDir = [fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil]; 101 | if(!bCreateDir){ 102 | 103 | } 104 | } 105 | } 106 | 107 | //颜色转换图片 108 | + (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size { 109 | 110 | CGRect rect = CGRectMake(0, 0, size.width, size.height); 111 | 112 | UIGraphicsBeginImageContext(rect.size); 113 | 114 | CGContextRef context = UIGraphicsGetCurrentContext(); 115 | 116 | CGContextSetFillColorWithColor(context, [color CGColor]); 117 | 118 | CGContextFillRect(context, rect); 119 | 120 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 121 | 122 | UIGraphicsEndImageContext(); 123 | 124 | return image; 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Header.h 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #ifndef Header_h 10 | #define Header_h 11 | 12 | #import "MBProgressHUD.h" 13 | #import "UILabel+Extension.h" 14 | #import "CZHTool.h" 15 | #import "UIButton+Extension.h" 16 | /**宏定义打印*/ 17 | #ifdef DEBUG // 处于开发阶段 18 | #define CZHLog(...) NSLog(__VA_ARGS__) 19 | #else // 处于发布阶段 20 | #define CZHLog(...) 21 | #endif 22 | 23 | #define CZHGlobelNormalFont(__VA_ARGS__) ([UIFont systemFontOfSize:CZH_ScaleFont(__VA_ARGS__)]) 24 | /**颜色*/ 25 | #define CZHColor(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 26 | 27 | ///weakSelf 28 | #define CZHWeakSelf(type) __weak typeof(type) weak##type = type; 29 | #define CZHStrongSelf(type) __strong typeof(type) type = weak##type; 30 | 31 | /**宽度比例*/ 32 | #define CZH_ScaleWidth(__VA_ARGS__) ([UIScreen mainScreen].bounds.size.width/375)*(__VA_ARGS__) 33 | 34 | /**高度比例*/ 35 | #define CZH_ScaleHeight(__VA_ARGS__) ([UIScreen mainScreen].bounds.size.height/667)*(__VA_ARGS__) 36 | 37 | /**字体比例*/ 38 | #define CZH_ScaleFont(__VA_ARGS__) ([UIScreen mainScreen].bounds.size.width/375)*(__VA_ARGS__) 39 | 40 | /**屏幕宽度*/ 41 | #define ScreenWidth ([UIScreen mainScreen].bounds.size.width) 42 | /**屏幕高度*/ 43 | #define ScreenHeight ([UIScreen mainScreen].bounds.size.height) 44 | 45 | #endif /* Header_h */ 46 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/UIButton+Extension.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+Extension.h 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIButton (Extension) 12 | 13 | + (UIButton *)czh_buttonWithTarget:(id)target action:(SEL)action frame:(CGRect)frame imageName:(NSString *)imageName; 14 | 15 | + (UIButton *)czh_buttonWithTarget:(id)target action:(SEL)action frame:(CGRect)frame titleColor:(UIColor *)titleColor titleFont:(UIFont *)titleFont title:(NSString *)title; 16 | @end 17 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/UIButton+Extension.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+Extension.m 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import "UIButton+Extension.h" 10 | #import "Header.h" 11 | @implementation UIButton (Extension) 12 | 13 | + (UIButton *)czh_buttonWithTarget:(id)target action:(SEL)action frame:(CGRect)frame imageName:(NSString *)imageName titleColor:(UIColor *)titleColor titleFont:(UIFont *)titleFont backgroundColor:(UIColor *)backgroundColor cornerRadius:(CGFloat)cornerRadius borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor title:(NSString *)title { 14 | 15 | UIButton *button = [[UIButton alloc] init]; 16 | button.frame = frame; 17 | button.backgroundColor = backgroundColor; 18 | 19 | if (title.length) { 20 | [button setTitle:title forState:UIControlStateNormal]; 21 | [button setTitleColor:titleColor forState:UIControlStateNormal]; 22 | button.titleLabel.font = titleFont; 23 | } 24 | 25 | if (imageName.length) { 26 | [button setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal]; 27 | if (title.length) { 28 | 29 | button.titleEdgeInsets = UIEdgeInsetsMake(0, CZH_ScaleWidth(10), 0, 0); 30 | button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, CZH_ScaleWidth(10)); 31 | } 32 | } 33 | 34 | if (cornerRadius > 0) { 35 | button.layer.masksToBounds = YES; 36 | button.layer.cornerRadius = cornerRadius; 37 | } 38 | button.layer.borderWidth = borderWidth; 39 | button.layer.borderColor = borderColor.CGColor; 40 | [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside]; 41 | 42 | return button; 43 | } 44 | 45 | 46 | + (UIButton *)czh_buttonWithTarget:(id)target action:(SEL)action frame:(CGRect)frame titleColor:(UIColor *)titleColor titleFont:(UIFont *)titleFont title:(NSString *)title { 47 | return [UIButton czh_buttonWithTarget:target action:action frame:frame imageName:nil titleColor:titleColor titleFont:titleFont backgroundColor:nil cornerRadius:0 borderWidth:0 borderColor:nil title:title]; 48 | } 49 | 50 | 51 | + (UIButton *)czh_buttonWithTarget:(id)target action:(SEL)action frame:(CGRect)frame imageName:(NSString *)imageName { 52 | return [UIButton czh_buttonWithTarget:target action:action frame:frame imageName:imageName titleColor:nil titleFont:0 backgroundColor:nil cornerRadius:0 borderWidth:0 borderColor:nil title:nil]; 53 | } 54 | @end 55 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/UILabel+Extension.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+Extension.h 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UILabel (Extension) 12 | 13 | + (UILabel *)setLabelWithFrame:(CGRect)frame font:(UIFont *)font textColor:(UIColor *)textColor textAliment:(NSTextAlignment)textAliment text:(NSString *)text; 14 | @end 15 | -------------------------------------------------------------------------------- /saveCover/CZHChooseCover/UILabel+Extension.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+Extension.m 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import "UILabel+Extension.h" 10 | 11 | @implementation UILabel (Extension) 12 | 13 | + (UILabel *)setLabelWithFrame:(CGRect)frame font:(UIFont *)font textColor:(UIColor *)textColor backgroundColor:(UIColor *)backgroundColor textAliment:(NSTextAlignment)textAliment shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset numberOfLines:(NSInteger)numberOfLines text:(NSString *)text { 14 | UILabel *label = [[UILabel alloc] init]; 15 | label.frame = frame; 16 | label.textAlignment = textAliment; 17 | label.text = text; 18 | label.numberOfLines = numberOfLines; 19 | label.shadowColor = shadowColor; 20 | label.shadowOffset = shadowOffset; 21 | if (backgroundColor == nil) { 22 | label.backgroundColor = [UIColor clearColor]; 23 | } else { 24 | label.backgroundColor = backgroundColor; 25 | } 26 | label.font = font; 27 | label.textColor = textColor; 28 | 29 | return label; 30 | } 31 | 32 | + (UILabel *)setLabelWithFrame:(CGRect)frame font:(UIFont *)font textColor:(UIColor *)textColor backgroundColor:(UIColor *)backgroundColor textAliment:(NSTextAlignment)textAliment shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset text:(NSString *)text { 33 | 34 | return [UILabel setLabelWithFrame:frame font:font textColor:textColor backgroundColor:backgroundColor textAliment:textAliment shadowColor:shadowColor shadowOffset:shadowOffset numberOfLines:1 text:text]; 35 | } 36 | 37 | + (UILabel *)setLabelWithFrame:(CGRect)frame font:(UIFont *)font textColor:(UIColor *)textColor backgroundColor:(UIColor *)backgroundColor textAliment:(NSTextAlignment)textAliment text:(NSString *)text { 38 | 39 | return [UILabel setLabelWithFrame:frame font:font textColor:textColor backgroundColor:backgroundColor textAliment:textAliment shadowColor:nil shadowOffset:CGSizeZero text:text]; 40 | } 41 | 42 | + (UILabel *)setLabelWithFrame:(CGRect)frame font:(UIFont *)font textColor:(UIColor *)textColor textAliment:(NSTextAlignment)textAliment text:(NSString *)text { 43 | return [UILabel setLabelWithFrame:frame font:font textColor:textColor backgroundColor:[UIColor clearColor] textAliment:textAliment text:text]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /saveCover/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPhotoLibraryAddUsageDescription 6 | hahaha 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /saveCover/MBProgressHUD/MBProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.h 3 | // Version 0.9.2 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | // This code is distributed under the terms and conditions of the MIT license. 8 | 9 | // Copyright (c) 2009-2015 Matej Bukovinski 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | #import 30 | #import 31 | #import 32 | 33 | @class MBBackgroundView; 34 | @protocol MBProgressHUDDelegate; 35 | 36 | 37 | extern CGFloat const MBProgressMaxOffset; 38 | 39 | typedef NS_ENUM(NSInteger, MBProgressHUDMode) { 40 | /// UIActivityIndicatorView. 41 | MBProgressHUDModeIndeterminate, 42 | /// A round, pie-chart like, progress view. 43 | MBProgressHUDModeDeterminate, 44 | /// Horizontal progress bar. 45 | MBProgressHUDModeDeterminateHorizontalBar, 46 | /// Ring-shaped progress view. 47 | MBProgressHUDModeAnnularDeterminate, 48 | /// Shows a custom view. 49 | MBProgressHUDModeCustomView, 50 | /// Shows only labels. 51 | MBProgressHUDModeText 52 | }; 53 | 54 | typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) { 55 | /// Opacity animation 56 | MBProgressHUDAnimationFade, 57 | /// Opacity + scale animation (zoom in when appearing zoom out when disappearing) 58 | MBProgressHUDAnimationZoom, 59 | /// Opacity + scale animation (zoom out style) 60 | MBProgressHUDAnimationZoomOut, 61 | /// Opacity + scale animation (zoom in style) 62 | MBProgressHUDAnimationZoomIn 63 | }; 64 | 65 | typedef NS_ENUM(NSInteger, MBProgressHUDBackgroundStyle) { 66 | /// Solid color background 67 | MBProgressHUDBackgroundStyleSolidColor, 68 | /// UIVisualEffectView or UIToolbar.layer background view 69 | MBProgressHUDBackgroundStyleBlur 70 | }; 71 | 72 | typedef void (^MBProgressHUDCompletionBlock)(); 73 | 74 | 75 | NS_ASSUME_NONNULL_BEGIN 76 | 77 | 78 | /** 79 | * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. 80 | * 81 | * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. 82 | * The MBProgressHUD window spans over the entire space given to it by the initWithFrame: constructor and catches all 83 | * user input on this region, thereby preventing the user operations on components below the view. 84 | * 85 | * @note To still allow touches to pass through the HUD, you can set hud.userInteractionEnabled = NO. 86 | * @attention MBProgressHUD is a UI class and should therefore only be accessed on the main thread. 87 | */ 88 | @interface MBProgressHUD : UIView 89 | 90 | /** 91 | * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. 92 | * 93 | * @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden. 94 | * 95 | * @param view The view that the HUD will be added to 96 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 97 | * animations while appearing. 98 | * @return A reference to the created HUD. 99 | * 100 | * @see hideHUDForView:animated: 101 | * @see animationType 102 | */ 103 | + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; 104 | 105 | /// @name Showing and hiding 106 | 107 | /** 108 | * Finds the top-most HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. 109 | * 110 | * @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden. 111 | * 112 | * @param view The view that is going to be searched for a HUD subview. 113 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 114 | * animations while disappearing. 115 | * @return YES if a HUD was found and removed, NO otherwise. 116 | * 117 | * @see showHUDAddedTo:animated: 118 | * @see animationType 119 | */ 120 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; 121 | 122 | /** 123 | * Finds the top-most HUD subview and returns it. 124 | * 125 | * @param view The view that is going to be searched. 126 | * @return A reference to the last HUD subview discovered. 127 | */ 128 | + (nullable MBProgressHUD *)HUDForView:(UIView *)view; 129 | 130 | /** 131 | * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with 132 | * view.bounds as the parameter. 133 | * 134 | * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as 135 | * the HUD's superview (i.e., the view that the HUD will be added to). 136 | */ 137 | - (instancetype)initWithView:(UIView *)view; 138 | 139 | /** 140 | * Displays the HUD. 141 | * 142 | * @note You need to make sure that the main thread completes its run loop soon after this method call so that 143 | * the user interface can be updated. Call this method when your task is already set up to be executed in a new thread 144 | * (e.g., when using something like NSOperation or making an asynchronous call like NSURLRequest). 145 | * 146 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 147 | * animations while appearing. 148 | * 149 | * @see animationType 150 | */ 151 | - (void)showAnimated:(BOOL)animated; 152 | 153 | /** 154 | * Hides the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 155 | * hide the HUD when your task completes. 156 | * 157 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 158 | * animations while disappearing. 159 | * 160 | * @see animationType 161 | */ 162 | - (void)hideAnimated:(BOOL)animated; 163 | 164 | /** 165 | * Hides the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 166 | * hide the HUD when your task completes. 167 | * 168 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 169 | * animations while disappearing. 170 | * @param delay Delay in seconds until the HUD is hidden. 171 | * 172 | * @see animationType 173 | */ 174 | - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay; 175 | 176 | /** 177 | * The HUD delegate object. Receives HUD state notifications. 178 | */ 179 | @property (weak, nonatomic) id delegate; 180 | 181 | /** 182 | * Called after the HUD is hiden. 183 | */ 184 | @property (copy, nullable) MBProgressHUDCompletionBlock completionBlock; 185 | 186 | /* 187 | * Grace period is the time (in seconds) that the invoked method may be run without 188 | * showing the HUD. If the task finishes before the grace time runs out, the HUD will 189 | * not be shown at all. 190 | * This may be used to prevent HUD display for very short tasks. 191 | * Defaults to 0 (no grace time). 192 | */ 193 | @property (assign, nonatomic) NSTimeInterval graceTime; 194 | 195 | /** 196 | * The minimum time (in seconds) that the HUD is shown. 197 | * This avoids the problem of the HUD being shown and than instantly hidden. 198 | * Defaults to 0 (no minimum show time). 199 | */ 200 | @property (assign, nonatomic) NSTimeInterval minShowTime; 201 | 202 | /** 203 | * Removes the HUD from its parent view when hidden. 204 | * Defaults to NO. 205 | */ 206 | @property (assign, nonatomic) BOOL removeFromSuperViewOnHide; 207 | 208 | /// @name Appearance 209 | 210 | /** 211 | * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. 212 | */ 213 | @property (assign, nonatomic) MBProgressHUDMode mode; 214 | 215 | /** 216 | * A color that gets forwarded to all labels and supported indicators. Also sets the tintColor 217 | * for custom views on iOS 7+. Set to nil to manage color individually. 218 | * Defaults to semi-translucent black on iOS 7 and later and white on earlier iOS versions. 219 | */ 220 | @property (strong, nonatomic, nullable) UIColor *contentColor UI_APPEARANCE_SELECTOR; 221 | 222 | /** 223 | * The animation type that should be used when the HUD is shown and hidden. 224 | */ 225 | @property (assign, nonatomic) MBProgressHUDAnimation animationType UI_APPEARANCE_SELECTOR; 226 | 227 | /** 228 | * The bezel offset relative to the center of the view. You can use MBProgressMaxOffset 229 | * and -MBProgressMaxOffset to move the HUD all the way to the screen edge in each direction. 230 | * E.g., CGPointMake(0.f, MBProgressMaxOffset) would position the HUD centered on the bottom edge. 231 | */ 232 | @property (assign, nonatomic) CGPoint offset UI_APPEARANCE_SELECTOR; 233 | 234 | /** 235 | * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). 236 | * This also represents the minimum bezel distance to the edge of the HUD view. 237 | * Defaults to 20.f 238 | */ 239 | @property (assign, nonatomic) CGFloat margin UI_APPEARANCE_SELECTOR; 240 | 241 | /** 242 | * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). 243 | */ 244 | @property (assign, nonatomic) CGSize minSize UI_APPEARANCE_SELECTOR; 245 | 246 | /** 247 | * Force the HUD dimensions to be equal if possible. 248 | */ 249 | @property (assign, nonatomic, getter = isSquare) BOOL square UI_APPEARANCE_SELECTOR; 250 | 251 | /** 252 | * When enabled, the bezel center gets slightly affected by the device accelerometer data. 253 | * Has no effect on iOS < 7.0. Defaults to YES. 254 | */ 255 | @property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled UI_APPEARANCE_SELECTOR; 256 | 257 | /// @name Progress 258 | 259 | /** 260 | * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. 261 | */ 262 | @property (assign, nonatomic) float progress; 263 | 264 | /// @name Views 265 | 266 | /** 267 | * The view containing the labels and indicator (or customView). 268 | */ 269 | @property (strong, nonatomic, readonly) MBBackgroundView *bezelView; 270 | 271 | /** 272 | * View covering the entire HUD area, placed behind bezelView. 273 | */ 274 | @property (strong, nonatomic, readonly) MBBackgroundView *backgroundView; 275 | 276 | /** 277 | * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. 278 | * The view should implement intrinsicContentSize for proper sizing. For best results use approximately 37 by 37 pixels. 279 | */ 280 | @property (strong, nonatomic, nullable) UIView *customView; 281 | 282 | /** 283 | * A label that holds an optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit 284 | * the entire text. 285 | */ 286 | @property (strong, nonatomic, readonly) UILabel *label; 287 | 288 | /** 289 | * A label that holds an optional details message displayed below the labelText message. The details text can span multiple lines. 290 | */ 291 | @property (strong, nonatomic, readonly) UILabel *detailsLabel; 292 | 293 | /** 294 | * A button that is placed below the labels. Visible only if a target / action is added. 295 | */ 296 | @property (strong, nonatomic, readonly) UIButton *button; 297 | 298 | @end 299 | 300 | 301 | @protocol MBProgressHUDDelegate 302 | 303 | @optional 304 | 305 | /** 306 | * Called after the HUD was fully hidden from the screen. 307 | */ 308 | - (void)hudWasHidden:(MBProgressHUD *)hud; 309 | 310 | @end 311 | 312 | 313 | /** 314 | * A progress view for showing definite progress by filling up a circle (pie chart). 315 | */ 316 | @interface MBRoundProgressView : UIView 317 | 318 | /** 319 | * Progress (0.0 to 1.0) 320 | */ 321 | @property (nonatomic, assign) float progress; 322 | 323 | /** 324 | * Indicator progress color. 325 | * Defaults to white [UIColor whiteColor]. 326 | */ 327 | @property (nonatomic, strong) UIColor *progressTintColor; 328 | 329 | /** 330 | * Indicator background (non-progress) color. 331 | * Only applicable on iOS versions older than iOS 7. 332 | * Defaults to translucent white (alpha 0.1). 333 | */ 334 | @property (nonatomic, strong) UIColor *backgroundTintColor; 335 | 336 | /* 337 | * Display mode - NO = round or YES = annular. Defaults to round. 338 | */ 339 | @property (nonatomic, assign, getter = isAnnular) BOOL annular; 340 | 341 | @end 342 | 343 | 344 | /** 345 | * A flat bar progress view. 346 | */ 347 | @interface MBBarProgressView : UIView 348 | 349 | /** 350 | * Progress (0.0 to 1.0) 351 | */ 352 | @property (nonatomic, assign) float progress; 353 | 354 | /** 355 | * Bar border line color. 356 | * Defaults to white [UIColor whiteColor]. 357 | */ 358 | @property (nonatomic, strong) UIColor *lineColor; 359 | 360 | /** 361 | * Bar background color. 362 | * Defaults to clear [UIColor clearColor]; 363 | */ 364 | @property (nonatomic, strong) UIColor *progressRemainingColor; 365 | 366 | /** 367 | * Bar progress color. 368 | * Defaults to white [UIColor whiteColor]. 369 | */ 370 | @property (nonatomic, strong) UIColor *progressColor; 371 | 372 | @end 373 | 374 | 375 | @interface MBBackgroundView : UIView 376 | 377 | /** 378 | * The background style. 379 | * Defaults to MBProgressHUDBackgroundStyleBlur on iOS 7 or later and MBProgressHUDBackgroundStyleSolidColor otherwise. 380 | * @note Due to iOS 7 not supporting UIVisualEffectView, the blur effect differs slightly between iOS 7 and later versions. 381 | */ 382 | @property (nonatomic) MBProgressHUDBackgroundStyle style; 383 | 384 | /** 385 | * The background color or the blur tint color. 386 | * @note Due to iOS 7 not supporting UIVisualEffectView, the blur effect differs slightly between iOS 7 and later versions. 387 | */ 388 | @property (nonatomic, strong) UIColor *color; 389 | 390 | @end 391 | 392 | @interface MBProgressHUD (Deprecated) 393 | 394 | + (NSArray *)allHUDsForView:(UIView *)view __attribute__((deprecated("Store references when using more than one HUD per view."))); 395 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated __attribute__((deprecated("Store references when using more than one HUD per view."))); 396 | 397 | - (id)initWithWindow:(UIWindow *)window __attribute__((deprecated("Use initWithView: instead."))); 398 | 399 | - (void)show:(BOOL)animated __attribute__((deprecated("Use showAnimated: instead."))); 400 | - (void)hide:(BOOL)animated __attribute__((deprecated("Use hideAnimated: instead."))); 401 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay __attribute__((deprecated("Use hideAnimated:afterDelay: instead."))); 402 | 403 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated __attribute__((deprecated("Use GCD directly."))); 404 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block __attribute__((deprecated("Use GCD directly."))); 405 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(nullable MBProgressHUDCompletionBlock)completion __attribute__((deprecated("Use GCD directly."))); 406 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue __attribute__((deprecated("Use GCD directly."))); 407 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 408 | completionBlock:(nullable MBProgressHUDCompletionBlock)completion __attribute__((deprecated("Use GCD directly."))); 409 | @property (assign) BOOL taskInProgress __attribute__((deprecated("No longer needed."))); 410 | 411 | @property (nonatomic, copy) NSString *labelText __attribute__((deprecated("Use label.text instead."))); 412 | @property (nonatomic, strong) UIFont *labelFont __attribute__((deprecated("Use label.font instead."))); 413 | @property (nonatomic, strong) UIColor *labelColor __attribute__((deprecated("Use label.textColor instead."))); 414 | @property (nonatomic, copy) NSString *detailsLabelText __attribute__((deprecated("Use detailsLabel.text instead."))); 415 | @property (nonatomic, strong) UIFont *detailsLabelFont __attribute__((deprecated("Use detailsLabel.font instead."))); 416 | @property (nonatomic, strong) UIColor *detailsLabelColor __attribute__((deprecated("Use detailsLabel.textColor instead."))); 417 | @property (assign, nonatomic) CGFloat opacity __attribute__((deprecated("Customize bezelView properties instead."))); 418 | @property (strong, nonatomic) UIColor *color __attribute__((deprecated("Customize the bezelView color instead."))); 419 | @property (assign, nonatomic) CGFloat xOffset __attribute__((deprecated("Set offset.x instead."))); 420 | @property (assign, nonatomic) CGFloat yOffset __attribute__((deprecated("Set offset.y instead."))); 421 | @property (assign, nonatomic) CGFloat cornerRadius __attribute__((deprecated("Set bezelView.layer.cornerRadius instead."))); 422 | @property (assign, nonatomic) BOOL dimBackground __attribute__((deprecated("Customize HUD background properties instead."))); 423 | @property (strong, nonatomic) UIColor *activityIndicatorColor __attribute__((deprecated("Use UIAppearance to customize UIActivityIndicatorView. E.g.: [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil].color = [UIColor redColor];"))); 424 | @property (atomic, assign, readonly) CGSize size __attribute__((deprecated("Get the bezelView.frame.size instead."))); 425 | 426 | @end 427 | 428 | NS_ASSUME_NONNULL_END 429 | -------------------------------------------------------------------------------- /saveCover/MBProgressHUD/MBProgressHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.m 3 | // Version 0.9.2 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | #import "MBProgressHUD.h" 8 | #import 9 | 10 | 11 | #ifndef kCFCoreFoundationVersionNumber_iOS_7_0 12 | #define kCFCoreFoundationVersionNumber_iOS_7_0 847.20 13 | #endif 14 | 15 | #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 16 | #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15 17 | #endif 18 | 19 | #define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread."); 20 | 21 | CGFloat const MBProgressMaxOffset = 1000000.f; 22 | 23 | static const CGFloat MBDefaultPadding = 4.f; 24 | static const CGFloat MBDefaultLabelFontSize = 16.f; 25 | static const CGFloat MBDefaultDetailsLabelFontSize = 12.f; 26 | 27 | 28 | @interface MBProgressHUD () { 29 | // Deprecated 30 | UIColor *_activityIndicatorColor; 31 | CGFloat _opacity; 32 | } 33 | 34 | @property (nonatomic, assign) BOOL useAnimation; 35 | @property (nonatomic, assign, getter=hasFinished) BOOL finished; 36 | @property (nonatomic, strong) UIView *indicator; 37 | @property (nonatomic, strong) NSDate *showStarted; 38 | @property (nonatomic, strong) NSArray *paddingConstraints; 39 | @property (nonatomic, strong) NSArray *bezelConstraints; 40 | @property (nonatomic, strong) UIView *topSpacer; 41 | @property (nonatomic, strong) UIView *bottomSpacer; 42 | @property (nonatomic, weak) NSTimer *graceTimer; 43 | @property (nonatomic, weak) NSTimer *minShowTimer; 44 | @property (nonatomic, weak) NSTimer *hideDelayTimer; 45 | 46 | // Deprecated 47 | @property (assign) BOOL taskInProgress; 48 | 49 | @end 50 | 51 | 52 | @interface MBProgressHUDRoundedButton : UIButton 53 | @end 54 | 55 | 56 | @implementation MBProgressHUD 57 | 58 | #pragma mark - Class methods 59 | 60 | + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { 61 | MBProgressHUD *hud = [[self alloc] initWithView:view]; 62 | hud.removeFromSuperViewOnHide = YES; 63 | [view addSubview:hud]; 64 | [hud showAnimated:animated]; 65 | return hud; 66 | } 67 | 68 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { 69 | MBProgressHUD *hud = [self HUDForView:view]; 70 | if (hud != nil) { 71 | hud.removeFromSuperViewOnHide = YES; 72 | [hud hideAnimated:animated]; 73 | return YES; 74 | } 75 | return NO; 76 | } 77 | 78 | + (MBProgressHUD *)HUDForView:(UIView *)view { 79 | NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; 80 | for (UIView *subview in subviewsEnum) { 81 | if ([subview isKindOfClass:self]) { 82 | return (MBProgressHUD *)subview; 83 | } 84 | } 85 | return nil; 86 | } 87 | 88 | #pragma mark - Lifecycle 89 | 90 | - (void)commonInit { 91 | // Set default values for properties 92 | _animationType = MBProgressHUDAnimationFade; 93 | _mode = MBProgressHUDModeIndeterminate; 94 | _margin = 20.0f; 95 | _opacity = 1.f; 96 | _defaultMotionEffectsEnabled = YES; 97 | 98 | // Default color, depending on the current iOS version 99 | BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; 100 | _contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f]; 101 | // Transparent background 102 | self.opaque = NO; 103 | self.backgroundColor = [UIColor clearColor]; 104 | // Make it invisible for now 105 | self.alpha = 0.0f; 106 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 107 | self.layer.allowsGroupOpacity = NO; 108 | 109 | [self setupViews]; 110 | [self updateIndicators]; 111 | [self registerForNotifications]; 112 | } 113 | 114 | - (instancetype)initWithFrame:(CGRect)frame { 115 | if ((self = [super initWithFrame:frame])) { 116 | [self commonInit]; 117 | } 118 | return self; 119 | } 120 | 121 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 122 | if ((self = [super initWithCoder:aDecoder])) { 123 | [self commonInit]; 124 | } 125 | return self; 126 | } 127 | 128 | - (id)initWithView:(UIView *)view { 129 | NSAssert(view, @"View must not be nil."); 130 | return [self initWithFrame:view.bounds]; 131 | } 132 | 133 | - (void)dealloc { 134 | [self unregisterFromNotifications]; 135 | } 136 | 137 | #pragma mark - Show & hide 138 | 139 | - (void)showAnimated:(BOOL)animated { 140 | MBMainThreadAssert(); 141 | [self.minShowTimer invalidate]; 142 | self.useAnimation = animated; 143 | self.finished = NO; 144 | // If the grace time is set, postpone the HUD display 145 | if (self.graceTime > 0.0) { 146 | NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; 147 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 148 | self.graceTimer = timer; 149 | } 150 | // ... otherwise show the HUD immediately 151 | else { 152 | [self showUsingAnimation:self.useAnimation]; 153 | } 154 | } 155 | 156 | - (void)hideAnimated:(BOOL)animated { 157 | MBMainThreadAssert(); 158 | [self.graceTimer invalidate]; 159 | self.useAnimation = animated; 160 | self.finished = YES; 161 | // If the minShow time is set, calculate how long the HUD was shown, 162 | // and postpone the hiding operation if necessary 163 | if (self.minShowTime > 0.0 && self.showStarted) { 164 | NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted]; 165 | if (interv < self.minShowTime) { 166 | NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; 167 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 168 | self.minShowTimer = timer; 169 | return; 170 | } 171 | } 172 | // ... otherwise hide the HUD immediately 173 | [self hideUsingAnimation:self.useAnimation]; 174 | } 175 | 176 | - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay { 177 | NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO]; 178 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 179 | self.hideDelayTimer = timer; 180 | } 181 | 182 | #pragma mark - Timer callbacks 183 | 184 | - (void)handleGraceTimer:(NSTimer *)theTimer { 185 | // Show the HUD only if the task is still running 186 | if (!self.hasFinished) { 187 | [self showUsingAnimation:self.useAnimation]; 188 | } 189 | } 190 | 191 | - (void)handleMinShowTimer:(NSTimer *)theTimer { 192 | [self hideUsingAnimation:self.useAnimation]; 193 | } 194 | 195 | - (void)handleHideTimer:(NSTimer *)timer { 196 | [self hideAnimated:[timer.userInfo boolValue]]; 197 | } 198 | 199 | #pragma mark - View Hierrarchy 200 | 201 | - (void)didMoveToSuperview { 202 | [self updateForCurrentOrientationAnimated:NO]; 203 | } 204 | 205 | #pragma mark - Internal show & hide operations 206 | 207 | - (void)showUsingAnimation:(BOOL)animated { 208 | // Cancel any previous animations 209 | [self.bezelView.layer removeAllAnimations]; 210 | [self.backgroundView.layer removeAllAnimations]; 211 | 212 | // Cancel any scheduled hideDelayed: calls 213 | [self.hideDelayTimer invalidate]; 214 | 215 | self.showStarted = [NSDate date]; 216 | self.alpha = 1.f; 217 | 218 | if (animated) { 219 | [self animateIn:YES withType:self.animationType completion:NULL]; 220 | } else { 221 | #pragma clang diagnostic push 222 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 223 | self.bezelView.alpha = self.opacity; 224 | #pragma clang diagnostic pop 225 | self.backgroundView.alpha = 1.f; 226 | } 227 | } 228 | 229 | - (void)hideUsingAnimation:(BOOL)animated { 230 | if (animated && self.showStarted) { 231 | self.showStarted = nil; 232 | [self animateIn:NO withType:self.animationType completion:^(BOOL finished) { 233 | [self done]; 234 | }]; 235 | } else { 236 | self.showStarted = nil; 237 | self.bezelView.alpha = 0.f; 238 | self.backgroundView.alpha = 1.f; 239 | [self done]; 240 | } 241 | } 242 | 243 | - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion { 244 | // Automatically determine the correct zoom animation type 245 | if (type == MBProgressHUDAnimationZoom) { 246 | type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut; 247 | } 248 | 249 | CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f); 250 | CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f); 251 | 252 | // Set starting state 253 | UIView *bezelView = self.bezelView; 254 | if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) { 255 | bezelView.transform = small; 256 | } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) { 257 | bezelView.transform = large; 258 | } 259 | 260 | // Perform animations 261 | dispatch_block_t animations = ^{ 262 | if (animatingIn) { 263 | bezelView.transform = CGAffineTransformIdentity; 264 | } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) { 265 | bezelView.transform = large; 266 | } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) { 267 | bezelView.transform = small; 268 | } 269 | #pragma clang diagnostic push 270 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 271 | bezelView.alpha = animatingIn ? self.opacity : 0.f; 272 | #pragma clang diagnostic pop 273 | self.backgroundView.alpha = animatingIn ? 1.f : 0.f; 274 | }; 275 | 276 | // Spring animations are nicer, but only available on iOS 7+ 277 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV 278 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) { 279 | [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion]; 280 | return; 281 | } 282 | #endif 283 | [UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion]; 284 | } 285 | 286 | - (void)done { 287 | // Cancel any scheduled hideDelayed: calls 288 | [self.hideDelayTimer invalidate]; 289 | 290 | if (self.hasFinished) { 291 | self.alpha = 0.0f; 292 | if (self.removeFromSuperViewOnHide) { 293 | [self removeFromSuperview]; 294 | } 295 | } 296 | MBProgressHUDCompletionBlock completionBlock = self.completionBlock; 297 | if (completionBlock) { 298 | completionBlock(); 299 | } 300 | id delegate = self.delegate; 301 | if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { 302 | [delegate performSelector:@selector(hudWasHidden:) withObject:self]; 303 | } 304 | } 305 | 306 | #pragma mark - UI 307 | 308 | - (void)setupViews { 309 | UIColor *defaultColor = self.contentColor; 310 | 311 | MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds]; 312 | backgroundView.style = MBProgressHUDBackgroundStyleSolidColor; 313 | backgroundView.backgroundColor = [UIColor clearColor]; 314 | backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 315 | backgroundView.alpha = 0.f; 316 | [self addSubview:backgroundView]; 317 | _backgroundView = backgroundView; 318 | 319 | MBBackgroundView *bezelView = [MBBackgroundView new]; 320 | bezelView.translatesAutoresizingMaskIntoConstraints = NO; 321 | bezelView.layer.cornerRadius = 5.f; 322 | bezelView.alpha = 0.f; 323 | [self addSubview:bezelView]; 324 | _bezelView = bezelView; 325 | [self updateBezelMotionEffects]; 326 | 327 | UILabel *label = [UILabel new]; 328 | label.adjustsFontSizeToFitWidth = NO; 329 | label.textAlignment = NSTextAlignmentCenter; 330 | label.textColor = defaultColor; 331 | label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize]; 332 | label.opaque = NO; 333 | label.backgroundColor = [UIColor clearColor]; 334 | _label = label; 335 | 336 | UILabel *detailsLabel = [UILabel new]; 337 | detailsLabel.adjustsFontSizeToFitWidth = NO; 338 | detailsLabel.textAlignment = NSTextAlignmentCenter; 339 | detailsLabel.textColor = defaultColor; 340 | detailsLabel.numberOfLines = 0; 341 | detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; 342 | detailsLabel.opaque = NO; 343 | detailsLabel.backgroundColor = [UIColor clearColor]; 344 | _detailsLabel = detailsLabel; 345 | 346 | UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom]; 347 | button.titleLabel.textAlignment = NSTextAlignmentCenter; 348 | button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; 349 | [button setTitleColor:defaultColor forState:UIControlStateNormal]; 350 | _button = button; 351 | 352 | for (UIView *view in @[label, detailsLabel, button]) { 353 | view.translatesAutoresizingMaskIntoConstraints = NO; 354 | [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; 355 | [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; 356 | [bezelView addSubview:view]; 357 | } 358 | 359 | UIView *topSpacer = [UIView new]; 360 | topSpacer.translatesAutoresizingMaskIntoConstraints = NO; 361 | topSpacer.hidden = YES; 362 | [bezelView addSubview:topSpacer]; 363 | _topSpacer = topSpacer; 364 | 365 | UIView *bottomSpacer = [UIView new]; 366 | bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO; 367 | bottomSpacer.hidden = YES; 368 | [bezelView addSubview:bottomSpacer]; 369 | _bottomSpacer = bottomSpacer; 370 | } 371 | 372 | - (void)updateIndicators { 373 | UIView *indicator = self.indicator; 374 | BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; 375 | BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; 376 | 377 | MBProgressHUDMode mode = self.mode; 378 | if (mode == MBProgressHUDModeIndeterminate) { 379 | if (!isActivityIndicator) { 380 | // Update to indeterminate indicator 381 | [indicator removeFromSuperview]; 382 | indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; 383 | [(UIActivityIndicatorView *)indicator startAnimating]; 384 | [self.bezelView addSubview:indicator]; 385 | } 386 | } 387 | else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { 388 | // Update to bar determinate indicator 389 | [indicator removeFromSuperview]; 390 | indicator = [[MBBarProgressView alloc] init]; 391 | [self.bezelView addSubview:indicator]; 392 | } 393 | else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { 394 | if (!isRoundIndicator) { 395 | // Update to determinante indicator 396 | [indicator removeFromSuperview]; 397 | indicator = [[MBRoundProgressView alloc] init]; 398 | [self.bezelView addSubview:indicator]; 399 | } 400 | if (mode == MBProgressHUDModeAnnularDeterminate) { 401 | [(MBRoundProgressView *)indicator setAnnular:YES]; 402 | } 403 | } 404 | else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) { 405 | // Update custom view indicator 406 | [indicator removeFromSuperview]; 407 | indicator = self.customView; 408 | [self.bezelView addSubview:indicator]; 409 | } 410 | else if (mode == MBProgressHUDModeText) { 411 | [indicator removeFromSuperview]; 412 | indicator = nil; 413 | } 414 | indicator.translatesAutoresizingMaskIntoConstraints = NO; 415 | self.indicator = indicator; 416 | 417 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 418 | [(id)indicator setValue:@(self.progress) forKey:@"progress"]; 419 | } 420 | 421 | [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; 422 | [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; 423 | 424 | [self updateViewsForColor:self.contentColor]; 425 | [self setNeedsUpdateConstraints]; 426 | } 427 | 428 | - (void)updateViewsForColor:(UIColor *)color { 429 | if (!color) return; 430 | 431 | self.label.textColor = color; 432 | self.detailsLabel.textColor = color; 433 | [self.button setTitleColor:color forState:UIControlStateNormal]; 434 | 435 | #pragma clang diagnostic push 436 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 437 | if (self.activityIndicatorColor) { 438 | color = self.activityIndicatorColor; 439 | } 440 | #pragma clang diagnostic pop 441 | 442 | // UIAppearance settings are prioritized. If they are preset the set color is ignored. 443 | 444 | UIView *indicator = self.indicator; 445 | if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) { 446 | UIActivityIndicatorView *appearance = nil; 447 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 448 | appearance = [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil]; 449 | #else 450 | // For iOS 9+ 451 | appearance = [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; 452 | #endif 453 | 454 | if (appearance.color == nil) { 455 | ((UIActivityIndicatorView *)indicator).color = color; 456 | } 457 | } else if ([indicator isKindOfClass:[MBRoundProgressView class]]) { 458 | MBRoundProgressView *appearance = nil; 459 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 460 | appearance = [MBRoundProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; 461 | #else 462 | appearance = [MBRoundProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; 463 | #endif 464 | if (appearance.progressTintColor == nil) { 465 | ((MBRoundProgressView *)indicator).progressTintColor = color; 466 | } 467 | if (appearance.backgroundTintColor == nil) { 468 | ((MBRoundProgressView *)indicator).backgroundTintColor = [color colorWithAlphaComponent:0.1]; 469 | } 470 | } else if ([indicator isKindOfClass:[MBBarProgressView class]]) { 471 | MBBarProgressView *appearance = nil; 472 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 473 | appearance = [MBBarProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; 474 | #else 475 | appearance = [MBBarProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; 476 | #endif 477 | if (appearance.progressColor == nil) { 478 | ((MBBarProgressView *)indicator).progressColor = color; 479 | } 480 | if (appearance.lineColor == nil) { 481 | ((MBBarProgressView *)indicator).lineColor = color; 482 | } 483 | } else { 484 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV 485 | if ([indicator respondsToSelector:@selector(setTintColor:)]) { 486 | [indicator setTintColor:color]; 487 | } 488 | #endif 489 | } 490 | } 491 | 492 | - (void)updateBezelMotionEffects { 493 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV 494 | MBBackgroundView *bezelView = self.bezelView; 495 | if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return; 496 | 497 | if (self.defaultMotionEffectsEnabled) { 498 | CGFloat effectOffset = 10.f; 499 | UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; 500 | effectX.maximumRelativeValue = @(effectOffset); 501 | effectX.minimumRelativeValue = @(-effectOffset); 502 | 503 | UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; 504 | effectY.maximumRelativeValue = @(effectOffset); 505 | effectY.minimumRelativeValue = @(-effectOffset); 506 | 507 | UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init]; 508 | group.motionEffects = @[effectX, effectY]; 509 | 510 | [bezelView addMotionEffect:group]; 511 | } else { 512 | NSArray *effects = [bezelView motionEffects]; 513 | for (UIMotionEffect *effect in effects) { 514 | [bezelView removeMotionEffect:effect]; 515 | } 516 | } 517 | #endif 518 | } 519 | 520 | #pragma mark - Layout 521 | 522 | - (void)updateConstraints { 523 | UIView *bezel = self.bezelView; 524 | UIView *topSpacer = self.topSpacer; 525 | UIView *bottomSpacer = self.bottomSpacer; 526 | CGFloat margin = self.margin; 527 | NSMutableArray *bezelConstraints = [NSMutableArray array]; 528 | NSDictionary *metrics = @{@"margin": @(margin)}; 529 | 530 | NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil]; 531 | if (self.indicator) [subviews insertObject:self.indicator atIndex:1]; 532 | 533 | // Remove existing constraints 534 | [self removeConstraints:self.constraints]; 535 | [topSpacer removeConstraints:topSpacer.constraints]; 536 | [bottomSpacer removeConstraints:bottomSpacer.constraints]; 537 | if (self.bezelConstraints) { 538 | [bezel removeConstraints:self.bezelConstraints]; 539 | self.bezelConstraints = nil; 540 | } 541 | 542 | // Center bezel in container (self), applying the offset if set 543 | CGPoint offset = self.offset; 544 | NSMutableArray *centeringConstraints = [NSMutableArray array]; 545 | [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]]; 546 | [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]]; 547 | [self applyPriority:998.f toConstraints:centeringConstraints]; 548 | [self addConstraints:centeringConstraints]; 549 | 550 | // Ensure minimum side margin is kept 551 | NSMutableArray *sideConstraints = [NSMutableArray array]; 552 | [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; 553 | [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; 554 | [self applyPriority:999.f toConstraints:sideConstraints]; 555 | [self addConstraints:sideConstraints]; 556 | 557 | // Minimum bezel size, if set 558 | CGSize minimumSize = self.minSize; 559 | if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) { 560 | NSMutableArray *minSizeConstraints = [NSMutableArray array]; 561 | [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]]; 562 | [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]]; 563 | [self applyPriority:997.f toConstraints:minSizeConstraints]; 564 | [bezelConstraints addObjectsFromArray:minSizeConstraints]; 565 | } 566 | 567 | // Square aspect ratio, if set 568 | if (self.square) { 569 | NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0]; 570 | square.priority = 997.f; 571 | [bezelConstraints addObject:square]; 572 | } 573 | 574 | // Top and bottom spacing 575 | [topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; 576 | [bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; 577 | // Top and bottom spaces should be equal 578 | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]]; 579 | 580 | // Layout subviews in bezel 581 | NSMutableArray *paddingConstraints = [NSMutableArray new]; 582 | [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { 583 | // Center in bezel 584 | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]]; 585 | // Ensure the minimum edge margin is kept 586 | [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]]; 587 | // Element spacing 588 | if (idx == 0) { 589 | // First, ensure spacing to bezel edge 590 | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]]; 591 | } else if (idx == subviews.count - 1) { 592 | // Last, ensure spacing to bezel edge 593 | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]]; 594 | } 595 | if (idx > 0) { 596 | // Has previous 597 | NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]; 598 | [bezelConstraints addObject:padding]; 599 | [paddingConstraints addObject:padding]; 600 | } 601 | }]; 602 | 603 | [bezel addConstraints:bezelConstraints]; 604 | self.bezelConstraints = bezelConstraints; 605 | 606 | self.paddingConstraints = [paddingConstraints copy]; 607 | [self updatePaddingConstraints]; 608 | 609 | [super updateConstraints]; 610 | } 611 | 612 | - (void)layoutSubviews { 613 | // There is no need to update constraints if they are going to 614 | // be recreated in [super layoutSubviews] due to needsUpdateConstraints being set. 615 | // This also avoids an issue on iOS 8, where updatePaddingConstraints 616 | // would trigger a zombie object access. 617 | if (!self.needsUpdateConstraints) { 618 | [self updatePaddingConstraints]; 619 | } 620 | [super layoutSubviews]; 621 | } 622 | 623 | - (void)updatePaddingConstraints { 624 | // Set padding dynamically, depending on whether the view is visible or not 625 | __block BOOL hasVisibleAncestors = NO; 626 | [self.paddingConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *padding, NSUInteger idx, BOOL *stop) { 627 | UIView *firstView = (UIView *)padding.firstItem; 628 | UIView *secondView = (UIView *)padding.secondItem; 629 | BOOL firstVisible = !firstView.hidden && !CGSizeEqualToSize(firstView.intrinsicContentSize, CGSizeZero); 630 | BOOL secondVisible = !secondView.hidden && !CGSizeEqualToSize(secondView.intrinsicContentSize, CGSizeZero); 631 | // Set if both views are visible or if there's a visible view on top that doesn't have padding 632 | // added relative to the current view yet 633 | padding.constant = (firstVisible && (secondVisible || hasVisibleAncestors)) ? MBDefaultPadding : 0.f; 634 | hasVisibleAncestors |= secondVisible; 635 | }]; 636 | } 637 | 638 | - (void)applyPriority:(UILayoutPriority)priority toConstraints:(NSArray *)constraints { 639 | for (NSLayoutConstraint *constraint in constraints) { 640 | constraint.priority = priority; 641 | } 642 | } 643 | 644 | #pragma mark - Properties 645 | 646 | - (void)setMode:(MBProgressHUDMode)mode { 647 | if (mode != _mode) { 648 | _mode = mode; 649 | [self updateIndicators]; 650 | } 651 | } 652 | 653 | - (void)setCustomView:(UIView *)customView { 654 | if (customView != _customView) { 655 | _customView = customView; 656 | if (self.mode == MBProgressHUDModeCustomView) { 657 | [self updateIndicators]; 658 | } 659 | } 660 | } 661 | 662 | - (void)setOffset:(CGPoint)offset { 663 | if (!CGPointEqualToPoint(offset, _offset)) { 664 | _offset = offset; 665 | [self setNeedsUpdateConstraints]; 666 | } 667 | } 668 | 669 | - (void)setMargin:(CGFloat)margin { 670 | if (margin != _margin) { 671 | _margin = margin; 672 | [self setNeedsUpdateConstraints]; 673 | } 674 | } 675 | 676 | - (void)setMinSize:(CGSize)minSize { 677 | if (!CGSizeEqualToSize(minSize, _minSize)) { 678 | _minSize = minSize; 679 | [self setNeedsUpdateConstraints]; 680 | } 681 | } 682 | 683 | - (void)setSquare:(BOOL)square { 684 | if (square != _square) { 685 | _square = square; 686 | [self setNeedsUpdateConstraints]; 687 | } 688 | } 689 | 690 | - (void)setProgress:(float)progress { 691 | if (progress != _progress) { 692 | _progress = progress; 693 | UIView *indicator = self.indicator; 694 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 695 | [(id)indicator setValue:@(self.progress) forKey:@"progress"]; 696 | } 697 | } 698 | } 699 | 700 | - (void)setContentColor:(UIColor *)contentColor { 701 | if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) { 702 | _contentColor = contentColor; 703 | [self updateViewsForColor:contentColor]; 704 | } 705 | } 706 | 707 | - (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled { 708 | if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) { 709 | _defaultMotionEffectsEnabled = defaultMotionEffectsEnabled; 710 | [self updateBezelMotionEffects]; 711 | } 712 | } 713 | 714 | #pragma mark - Notifications 715 | 716 | - (void)registerForNotifications { 717 | #if !TARGET_OS_TV 718 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 719 | 720 | [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) 721 | name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 722 | #endif 723 | } 724 | 725 | - (void)unregisterFromNotifications { 726 | #if !TARGET_OS_TV 727 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 728 | [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 729 | #endif 730 | } 731 | 732 | #if !TARGET_OS_TV 733 | - (void)statusBarOrientationDidChange:(NSNotification *)notification { 734 | UIView *superview = self.superview; 735 | if (!superview) { 736 | return; 737 | } else { 738 | [self updateForCurrentOrientationAnimated:YES]; 739 | } 740 | } 741 | #endif 742 | 743 | - (void)updateForCurrentOrientationAnimated:(BOOL)animated { 744 | // Stay in sync with the superview in any case 745 | if (self.superview) { 746 | self.bounds = self.superview.bounds; 747 | } 748 | 749 | // Not needed on iOS 8+, compile out when the deployment target allows, 750 | // to avoid sharedApplication problems on extension targets 751 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 752 | // Only needed pre iOS 8 when added to a window 753 | BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0; 754 | if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return; 755 | 756 | // Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check. 757 | // This just ensures we don't get a warning about extension-unsafe API. 758 | Class UIApplicationClass = NSClassFromString(@"UIApplication"); 759 | if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return; 760 | 761 | UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; 762 | UIInterfaceOrientation orientation = application.statusBarOrientation; 763 | CGFloat radians = 0; 764 | 765 | if (UIInterfaceOrientationIsLandscape(orientation)) { 766 | radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2; 767 | // Window coordinates differ! 768 | self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); 769 | } else { 770 | radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f; 771 | } 772 | 773 | if (animated) { 774 | [UIView animateWithDuration:0.3 animations:^{ 775 | self.transform = CGAffineTransformMakeRotation(radians); 776 | }]; 777 | } else { 778 | self.transform = CGAffineTransformMakeRotation(radians); 779 | } 780 | #endif 781 | } 782 | 783 | @end 784 | 785 | 786 | @implementation MBRoundProgressView 787 | 788 | #pragma mark - Lifecycle 789 | 790 | - (id)init { 791 | return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; 792 | } 793 | 794 | - (id)initWithFrame:(CGRect)frame { 795 | self = [super initWithFrame:frame]; 796 | if (self) { 797 | self.backgroundColor = [UIColor clearColor]; 798 | self.opaque = NO; 799 | _progress = 0.f; 800 | _annular = NO; 801 | _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; 802 | _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; 803 | } 804 | return self; 805 | } 806 | 807 | #pragma mark - Layout 808 | 809 | - (CGSize)intrinsicContentSize { 810 | return CGSizeMake(37.f, 37.f); 811 | } 812 | 813 | #pragma mark - Properties 814 | 815 | - (void)setProgress:(float)progress { 816 | if (progress != _progress) { 817 | _progress = progress; 818 | [self setNeedsDisplay]; 819 | } 820 | } 821 | 822 | - (void)setProgressTintColor:(UIColor *)progressTintColor { 823 | NSAssert(progressTintColor, @"The color should not be nil."); 824 | if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) { 825 | _progressTintColor = progressTintColor; 826 | [self setNeedsDisplay]; 827 | } 828 | } 829 | 830 | - (void)setBackgroundTintColor:(UIColor *)backgroundTintColor { 831 | NSAssert(backgroundTintColor, @"The color should not be nil."); 832 | if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) { 833 | _backgroundTintColor = backgroundTintColor; 834 | [self setNeedsDisplay]; 835 | } 836 | } 837 | 838 | #pragma mark - Drawing 839 | 840 | - (void)drawRect:(CGRect)rect { 841 | CGContextRef context = UIGraphicsGetCurrentContext(); 842 | BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; 843 | 844 | if (_annular) { 845 | // Draw background 846 | CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f; 847 | UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; 848 | processBackgroundPath.lineWidth = lineWidth; 849 | processBackgroundPath.lineCapStyle = kCGLineCapButt; 850 | CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); 851 | CGFloat radius = (self.bounds.size.width - lineWidth)/2; 852 | CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees 853 | CGFloat endAngle = (2 * (float)M_PI) + startAngle; 854 | [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 855 | [_backgroundTintColor set]; 856 | [processBackgroundPath stroke]; 857 | // Draw progress 858 | UIBezierPath *processPath = [UIBezierPath bezierPath]; 859 | processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare; 860 | processPath.lineWidth = lineWidth; 861 | endAngle = (self.progress * 2 * (float)M_PI) + startAngle; 862 | [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 863 | [_progressTintColor set]; 864 | [processPath stroke]; 865 | } else { 866 | // Draw background 867 | CGFloat lineWidth = 2.f; 868 | CGRect allRect = self.bounds; 869 | CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f); 870 | CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); 871 | [_progressTintColor setStroke]; 872 | [_backgroundTintColor setFill]; 873 | CGContextSetLineWidth(context, lineWidth); 874 | if (isPreiOS7) { 875 | CGContextFillEllipseInRect(context, circleRect); 876 | } 877 | CGContextStrokeEllipseInRect(context, circleRect); 878 | // 90 degrees 879 | CGFloat startAngle = - ((float)M_PI / 2.f); 880 | // Draw progress 881 | if (isPreiOS7) { 882 | CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - lineWidth; 883 | CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle; 884 | [_progressTintColor setFill]; 885 | CGContextMoveToPoint(context, center.x, center.y); 886 | CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); 887 | CGContextClosePath(context); 888 | CGContextFillPath(context); 889 | } else { 890 | UIBezierPath *processPath = [UIBezierPath bezierPath]; 891 | processPath.lineCapStyle = kCGLineCapButt; 892 | processPath.lineWidth = lineWidth * 2.f; 893 | CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f); 894 | CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle; 895 | [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 896 | // Ensure that we don't get color overlaping when _progressTintColor alpha < 1.f. 897 | CGContextSetBlendMode(context, kCGBlendModeCopy); 898 | [_progressTintColor set]; 899 | [processPath stroke]; 900 | } 901 | } 902 | } 903 | 904 | @end 905 | 906 | 907 | @implementation MBBarProgressView 908 | 909 | #pragma mark - Lifecycle 910 | 911 | - (id)init { 912 | return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)]; 913 | } 914 | 915 | - (id)initWithFrame:(CGRect)frame { 916 | self = [super initWithFrame:frame]; 917 | if (self) { 918 | _progress = 0.f; 919 | _lineColor = [UIColor whiteColor]; 920 | _progressColor = [UIColor whiteColor]; 921 | _progressRemainingColor = [UIColor clearColor]; 922 | self.backgroundColor = [UIColor clearColor]; 923 | self.opaque = NO; 924 | } 925 | return self; 926 | } 927 | 928 | #pragma mark - Layout 929 | 930 | - (CGSize)intrinsicContentSize { 931 | BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; 932 | return CGSizeMake(120.f, isPreiOS7 ? 20.f : 10.f); 933 | } 934 | 935 | #pragma mark - Properties 936 | 937 | - (void)setProgress:(float)progress { 938 | if (progress != _progress) { 939 | _progress = progress; 940 | [self setNeedsDisplay]; 941 | } 942 | } 943 | 944 | - (void)setProgressColor:(UIColor *)progressColor { 945 | NSAssert(progressColor, @"The color should not be nil."); 946 | if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) { 947 | _progressColor = progressColor; 948 | [self setNeedsDisplay]; 949 | } 950 | } 951 | 952 | - (void)setProgressRemainingColor:(UIColor *)progressRemainingColor { 953 | NSAssert(progressRemainingColor, @"The color should not be nil."); 954 | if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) { 955 | _progressRemainingColor = progressRemainingColor; 956 | [self setNeedsDisplay]; 957 | } 958 | } 959 | 960 | #pragma mark - Drawing 961 | 962 | - (void)drawRect:(CGRect)rect { 963 | CGContextRef context = UIGraphicsGetCurrentContext(); 964 | 965 | CGContextSetLineWidth(context, 2); 966 | CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]); 967 | CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]); 968 | 969 | // Draw background 970 | CGFloat radius = (rect.size.height / 2) - 2; 971 | CGContextMoveToPoint(context, 2, rect.size.height/2); 972 | CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); 973 | CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); 974 | CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); 975 | CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); 976 | CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); 977 | CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); 978 | CGContextFillPath(context); 979 | 980 | // Draw border 981 | CGContextMoveToPoint(context, 2, rect.size.height/2); 982 | CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); 983 | CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); 984 | CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); 985 | CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); 986 | CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); 987 | CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); 988 | CGContextStrokePath(context); 989 | 990 | CGContextSetFillColorWithColor(context, [_progressColor CGColor]); 991 | radius = radius - 2; 992 | CGFloat amount = self.progress * rect.size.width; 993 | 994 | // Progress in the middle area 995 | if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) { 996 | CGContextMoveToPoint(context, 4, rect.size.height/2); 997 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 998 | CGContextAddLineToPoint(context, amount, 4); 999 | CGContextAddLineToPoint(context, amount, radius + 4); 1000 | 1001 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1002 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 1003 | CGContextAddLineToPoint(context, amount, rect.size.height - 4); 1004 | CGContextAddLineToPoint(context, amount, radius + 4); 1005 | 1006 | CGContextFillPath(context); 1007 | } 1008 | 1009 | // Progress in the right arc 1010 | else if (amount > radius + 4) { 1011 | CGFloat x = amount - (rect.size.width - radius - 4); 1012 | 1013 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1014 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 1015 | CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4); 1016 | CGFloat angle = -acos(x/radius); 1017 | if (isnan(angle)) angle = 0; 1018 | CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0); 1019 | CGContextAddLineToPoint(context, amount, rect.size.height/2); 1020 | 1021 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1022 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 1023 | CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4); 1024 | angle = acos(x/radius); 1025 | if (isnan(angle)) angle = 0; 1026 | CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1); 1027 | CGContextAddLineToPoint(context, amount, rect.size.height/2); 1028 | 1029 | CGContextFillPath(context); 1030 | } 1031 | 1032 | // Progress is in the left arc 1033 | else if (amount < radius + 4 && amount > 0) { 1034 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1035 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 1036 | CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); 1037 | 1038 | CGContextMoveToPoint(context, 4, rect.size.height/2); 1039 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 1040 | CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); 1041 | 1042 | CGContextFillPath(context); 1043 | } 1044 | } 1045 | 1046 | @end 1047 | 1048 | 1049 | @interface MBBackgroundView () 1050 | 1051 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1052 | @property UIVisualEffectView *effectView; 1053 | #endif 1054 | @property UIToolbar *toolbar; 1055 | 1056 | @end 1057 | 1058 | 1059 | @implementation MBBackgroundView 1060 | 1061 | #pragma mark - Lifecycle 1062 | 1063 | - (instancetype)initWithFrame:(CGRect)frame { 1064 | if ((self = [super initWithFrame:frame])) { 1065 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) { 1066 | _style = MBProgressHUDBackgroundStyleBlur; 1067 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { 1068 | _color = [UIColor colorWithWhite:0.8f alpha:0.6f]; 1069 | 1070 | } else { 1071 | _color = [UIColor colorWithWhite:0.95f alpha:0.6f]; 1072 | 1073 | } 1074 | } else { 1075 | _style = MBProgressHUDBackgroundStyleSolidColor; 1076 | _color = [[UIColor blackColor] colorWithAlphaComponent:0.8]; 1077 | } 1078 | 1079 | self.clipsToBounds = YES; 1080 | 1081 | [self updateForBackgroundStyle]; 1082 | } 1083 | return self; 1084 | } 1085 | 1086 | #pragma mark - Layout 1087 | 1088 | - (CGSize)intrinsicContentSize { 1089 | // Smallest size possible. Content pushes against this. 1090 | return CGSizeZero; 1091 | } 1092 | 1093 | #pragma mark - Appearance 1094 | 1095 | - (void)setStyle:(MBProgressHUDBackgroundStyle)style { 1096 | if (style == MBProgressHUDBackgroundStyleBlur && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0) { 1097 | style = MBProgressHUDBackgroundStyleSolidColor; 1098 | } 1099 | if (_style != style) { 1100 | _style = style; 1101 | [self updateForBackgroundStyle]; 1102 | } 1103 | } 1104 | 1105 | - (void)setColor:(UIColor *)color { 1106 | NSAssert(color, @"The color should not be nil."); 1107 | if (color != _color && ![color isEqual:_color]) { 1108 | _color = color; 1109 | [self updateViewsForColor:color]; 1110 | } 1111 | } 1112 | 1113 | /////////////////////////////////////////////////////////////////////////////////////////// 1114 | #pragma mark - Views 1115 | 1116 | - (void)updateForBackgroundStyle { 1117 | MBProgressHUDBackgroundStyle style = self.style; 1118 | if (style == MBProgressHUDBackgroundStyleBlur) { 1119 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1120 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { 1121 | UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 1122 | UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect]; 1123 | [self addSubview:effectView]; 1124 | effectView.frame = self.bounds; 1125 | effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 1126 | self.backgroundColor = self.color; 1127 | self.layer.allowsGroupOpacity = NO; 1128 | self.effectView = effectView; 1129 | } else { 1130 | #endif 1131 | UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectInset(self.bounds, -100.f, -100.f)]; 1132 | toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 1133 | toolbar.barTintColor = self.color; 1134 | toolbar.translucent = YES; 1135 | [self addSubview:toolbar]; 1136 | self.toolbar = toolbar; 1137 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1138 | } 1139 | #endif 1140 | } else { 1141 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1142 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { 1143 | [self.effectView removeFromSuperview]; 1144 | self.effectView = nil; 1145 | } else { 1146 | #endif 1147 | [self.toolbar removeFromSuperview]; 1148 | self.toolbar = nil; 1149 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV 1150 | } 1151 | #endif 1152 | self.backgroundColor = self.color; 1153 | } 1154 | } 1155 | 1156 | - (void)updateViewsForColor:(UIColor *)color { 1157 | if (self.style == MBProgressHUDBackgroundStyleBlur) { 1158 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { 1159 | self.backgroundColor = self.color; 1160 | } else { 1161 | self.toolbar.barTintColor = color; 1162 | } 1163 | } else { 1164 | self.backgroundColor = self.color; 1165 | } 1166 | } 1167 | 1168 | @end 1169 | 1170 | 1171 | @implementation MBProgressHUD (Deprecated) 1172 | 1173 | #pragma mark - Class 1174 | 1175 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated { 1176 | NSArray *huds = [MBProgressHUD allHUDsForView:view]; 1177 | for (MBProgressHUD *hud in huds) { 1178 | hud.removeFromSuperViewOnHide = YES; 1179 | [hud hideAnimated:animated]; 1180 | } 1181 | return [huds count]; 1182 | } 1183 | 1184 | + (NSArray *)allHUDsForView:(UIView *)view { 1185 | NSMutableArray *huds = [NSMutableArray array]; 1186 | NSArray *subviews = view.subviews; 1187 | for (UIView *aView in subviews) { 1188 | if ([aView isKindOfClass:self]) { 1189 | [huds addObject:aView]; 1190 | } 1191 | } 1192 | return [NSArray arrayWithArray:huds]; 1193 | } 1194 | 1195 | #pragma mark - Lifecycle 1196 | 1197 | - (id)initWithWindow:(UIWindow *)window { 1198 | return [self initWithView:window]; 1199 | } 1200 | 1201 | #pragma mark - Show & hide 1202 | 1203 | - (void)show:(BOOL)animated { 1204 | [self showAnimated:animated]; 1205 | } 1206 | 1207 | - (void)hide:(BOOL)animated { 1208 | [self hideAnimated:animated]; 1209 | } 1210 | 1211 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay { 1212 | [self hideAnimated:animated afterDelay:delay]; 1213 | } 1214 | 1215 | #pragma mark - Threading 1216 | 1217 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { 1218 | [self showAnimated:animated whileExecutingBlock:^{ 1219 | #pragma clang diagnostic push 1220 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 1221 | // Start executing the requested task 1222 | [target performSelector:method withObject:object]; 1223 | #pragma clang diagnostic pop 1224 | }]; 1225 | } 1226 | 1227 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block { 1228 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 1229 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 1230 | } 1231 | 1232 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion { 1233 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 1234 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion]; 1235 | } 1236 | 1237 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue { 1238 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 1239 | } 1240 | 1241 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue completionBlock:(nullable MBProgressHUDCompletionBlock)completion { 1242 | self.taskInProgress = YES; 1243 | self.completionBlock = completion; 1244 | dispatch_async(queue, ^(void) { 1245 | block(); 1246 | dispatch_async(dispatch_get_main_queue(), ^(void) { 1247 | [self cleanUp]; 1248 | }); 1249 | }); 1250 | [self showAnimated:animated]; 1251 | } 1252 | 1253 | - (void)cleanUp { 1254 | self.taskInProgress = NO; 1255 | [self hideAnimated:self.useAnimation]; 1256 | } 1257 | 1258 | #pragma mark - Labels 1259 | 1260 | - (NSString *)labelText { 1261 | return self.label.text; 1262 | } 1263 | 1264 | - (void)setLabelText:(NSString *)labelText { 1265 | MBMainThreadAssert(); 1266 | self.label.text = labelText; 1267 | } 1268 | 1269 | - (UIFont *)labelFont { 1270 | return self.label.font; 1271 | } 1272 | 1273 | - (void)setLabelFont:(UIFont *)labelFont { 1274 | MBMainThreadAssert(); 1275 | self.label.font = labelFont; 1276 | } 1277 | 1278 | - (UIColor *)labelColor { 1279 | return self.label.textColor; 1280 | } 1281 | 1282 | - (void)setLabelColor:(UIColor *)labelColor { 1283 | MBMainThreadAssert(); 1284 | self.label.textColor = labelColor; 1285 | } 1286 | 1287 | - (NSString *)detailsLabelText { 1288 | return self.detailsLabel.text; 1289 | } 1290 | 1291 | - (void)setDetailsLabelText:(NSString *)detailsLabelText { 1292 | MBMainThreadAssert(); 1293 | self.detailsLabel.text = detailsLabelText; 1294 | } 1295 | 1296 | - (UIFont *)detailsLabelFont { 1297 | return self.detailsLabel.font; 1298 | } 1299 | 1300 | - (void)setDetailsLabelFont:(UIFont *)detailsLabelFont { 1301 | MBMainThreadAssert(); 1302 | self.detailsLabel.font = detailsLabelFont; 1303 | } 1304 | 1305 | - (UIColor *)detailsLabelColor { 1306 | return self.detailsLabel.textColor; 1307 | } 1308 | 1309 | - (void)setDetailsLabelColor:(UIColor *)detailsLabelColor { 1310 | MBMainThreadAssert(); 1311 | self.detailsLabel.textColor = detailsLabelColor; 1312 | } 1313 | 1314 | - (CGFloat)opacity { 1315 | return _opacity; 1316 | } 1317 | 1318 | - (void)setOpacity:(CGFloat)opacity { 1319 | MBMainThreadAssert(); 1320 | _opacity = opacity; 1321 | } 1322 | 1323 | - (UIColor *)color { 1324 | return self.bezelView.color; 1325 | } 1326 | 1327 | - (void)setColor:(UIColor *)color { 1328 | MBMainThreadAssert(); 1329 | self.bezelView.color = color; 1330 | } 1331 | 1332 | - (CGFloat)yOffset { 1333 | return self.offset.y; 1334 | } 1335 | 1336 | - (void)setYOffset:(CGFloat)yOffset { 1337 | MBMainThreadAssert(); 1338 | self.offset = CGPointMake(self.offset.x, yOffset); 1339 | } 1340 | 1341 | - (CGFloat)xOffset { 1342 | return self.offset.x; 1343 | } 1344 | 1345 | - (void)setXOffset:(CGFloat)xOffset { 1346 | MBMainThreadAssert(); 1347 | self.offset = CGPointMake(xOffset, self.offset.y); 1348 | } 1349 | 1350 | - (CGFloat)cornerRadius { 1351 | return self.bezelView.layer.cornerRadius; 1352 | } 1353 | 1354 | - (void)setCornerRadius:(CGFloat)cornerRadius { 1355 | MBMainThreadAssert(); 1356 | self.bezelView.layer.cornerRadius = cornerRadius; 1357 | } 1358 | 1359 | - (BOOL)dimBackground { 1360 | MBBackgroundView *backgroundView = self.backgroundView; 1361 | UIColor *dimmedColor = [UIColor colorWithWhite:0.f alpha:.2f]; 1362 | return backgroundView.style == MBProgressHUDBackgroundStyleSolidColor && [backgroundView.color isEqual:dimmedColor]; 1363 | } 1364 | 1365 | - (void)setDimBackground:(BOOL)dimBackground { 1366 | MBMainThreadAssert(); 1367 | self.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor; 1368 | self.backgroundView.color = dimBackground ? [UIColor colorWithWhite:0.f alpha:.2f] : [UIColor clearColor]; 1369 | } 1370 | 1371 | - (CGSize)size { 1372 | return self.bezelView.frame.size; 1373 | } 1374 | 1375 | - (UIColor *)activityIndicatorColor { 1376 | return _activityIndicatorColor; 1377 | } 1378 | 1379 | - (void)setActivityIndicatorColor:(UIColor *)activityIndicatorColor { 1380 | if (activityIndicatorColor != _activityIndicatorColor) { 1381 | _activityIndicatorColor = activityIndicatorColor; 1382 | UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)self.indicator; 1383 | if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) { 1384 | [indicator setColor:activityIndicatorColor]; 1385 | } 1386 | } 1387 | } 1388 | 1389 | @end 1390 | 1391 | @implementation MBProgressHUDRoundedButton 1392 | 1393 | #pragma mark - Lifecycle 1394 | 1395 | - (instancetype)initWithFrame:(CGRect)frame { 1396 | self = [super initWithFrame:frame]; 1397 | if (self) { 1398 | CALayer *layer = self.layer; 1399 | layer.borderWidth = 1.f; 1400 | } 1401 | return self; 1402 | } 1403 | 1404 | #pragma mark - Layout 1405 | 1406 | - (void)layoutSubviews { 1407 | [super layoutSubviews]; 1408 | // Fully rounded corners 1409 | CGFloat height = CGRectGetHeight(self.bounds); 1410 | self.layer.cornerRadius = ceil(height / 2.f); 1411 | } 1412 | 1413 | - (CGSize)intrinsicContentSize { 1414 | // Only show if we have associated control events 1415 | if (self.allControlEvents == 0) return CGSizeZero; 1416 | CGSize size = [super intrinsicContentSize]; 1417 | // Add some side padding 1418 | size.width += 20.f; 1419 | return size; 1420 | } 1421 | 1422 | #pragma mark - Color 1423 | 1424 | - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state { 1425 | [super setTitleColor:color forState:state]; 1426 | // Update related colors 1427 | [self setHighlighted:self.highlighted]; 1428 | self.layer.borderColor = color.CGColor; 1429 | } 1430 | 1431 | - (void)setHighlighted:(BOOL)highlighted { 1432 | [super setHighlighted:highlighted]; 1433 | UIColor *baseColor = [self titleColorForState:UIControlStateSelected]; 1434 | self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor]; 1435 | } 1436 | 1437 | @end 1438 | -------------------------------------------------------------------------------- /saveCover/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /saveCover/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import 11 | #import "CZHTool.h" 12 | #import "Header.h" 13 | #import "CZHChooseCoverController.h" 14 | 15 | @interface ViewController () 16 | 17 | @property (weak, nonatomic) IBOutlet UIImageView *coverImageView; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | 24 | 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | 29 | 30 | 31 | } 32 | //打开相册 33 | - (IBAction)openAlbum:(id)sender { 34 | 35 | [self openImagePickerController:UIImagePickerControllerSourceTypePhotoLibrary]; 36 | 37 | } 38 | 39 | 40 | //打开相册 41 | - (void)openImagePickerController:(UIImagePickerControllerSourceType)type 42 | { 43 | if (![UIImagePickerController isSourceTypeAvailable:type]) return; 44 | 45 | UIImagePickerController *ipc = [[UIImagePickerController alloc] init]; 46 | 47 | //视频最大时间 48 | ipc.videoMaximumDuration = 30; 49 | 50 | ipc.view.backgroundColor = [UIColor whiteColor]; 51 | ipc.sourceType = type; 52 | ipc.delegate = self; 53 | //只打开视频 54 | ipc.mediaTypes = @[(NSString *)kUTTypeMovie]; 55 | 56 | //视频上传质量 57 | ipc.videoQuality = UIImagePickerControllerQualityTypeHigh; 58 | 59 | [self presentViewController:ipc animated:YES completion:nil]; 60 | } 61 | 62 | 63 | #pragma mark - UIImagePickerControllerDelegate 64 | /** 65 | * 从UIImagePickerController选择完图片后就调用(拍照完毕或者选择相册图片完毕) 66 | */ 67 | 68 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 69 | 70 | CZHWeakSelf(self); 71 | 72 | NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType]; 73 | 74 | if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) { 75 | //如果是视频 76 | NSURL *url = info[UIImagePickerControllerMediaURL]; 77 | 78 | //计算相册视频时长 79 | NSDictionary *videoDic = [CZHTool getLocalVideoSizeAndTimeWithSourcePath:url.absoluteString]; 80 | 81 | int videoTime = [[videoDic valueForKey:@"duration"] intValue]; 82 | 83 | NSUInteger limitTime = 30; 84 | if (videoTime > limitTime) { 85 | 86 | [picker dismissViewControllerAnimated:YES completion:nil]; 87 | return; 88 | } 89 | [CZHTool convertMovTypeIntoMp4TypeWithSourceUrl:url convertSuccess:^(NSURL *path) { 90 | CZHStrongSelf(self); 91 | [picker dismissViewControllerAnimated:YES completion:nil]; 92 | 93 | 94 | CZHChooseCoverController *chooseCover = [[CZHChooseCoverController alloc] init]; 95 | chooseCover.videoPath = path; 96 | chooseCover.coverImageBlock = ^(UIImage *coverImage) { 97 | 98 | self.coverImageView.image = coverImage; 99 | 100 | //上传视频 101 | // [self changeWithUploadSource:path]; 102 | // //上传封面 103 | // [self uploadCoverWithImage:coverImage]; 104 | }; 105 | [self presentViewController:chooseCover animated:YES completion:nil]; 106 | 107 | }]; 108 | 109 | } 110 | } 111 | 112 | // 取消图片选择调用此方法 113 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { 114 | 115 | // dismiss UIImagePickerController 116 | [self dismissViewControllerAnimated:YES completion:nil]; 117 | } 118 | 119 | 120 | 121 | //保存本地视频到相册 122 | - (IBAction)saveVideoToAlbum:(id)sender { 123 | 124 | 125 | NSMutableArray *videoArray = [NSMutableArray array]; 126 | //工程中类型是MP4的文件数组 127 | NSArray *movs = [[NSBundle mainBundle] pathsForResourcesOfType:@"mp4" inDirectory:nil]; 128 | [videoArray addObjectsFromArray:movs]; 129 | for (id item in videoArray) { 130 | //循环保存到相册 131 | if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(item)) { 132 | UISaveVideoAtPathToSavedPhotosAlbum(item, self, nil, NULL); 133 | } 134 | } 135 | } 136 | 137 | - (void)saveAction 138 | { 139 | 140 | } 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /saveCover/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // saveCover 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /saveCover/video/haha.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolincheng/CZHChooseCoverController/e2771dadc64893e022d9ce18e7e0b7b651d57703/saveCover/video/haha.mp4 -------------------------------------------------------------------------------- /saveCoverTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /saveCoverTests/saveCoverTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // saveCoverTests.m 3 | // saveCoverTests 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface saveCoverTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation saveCoverTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /saveCoverUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /saveCoverUITests/saveCoverUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // saveCoverUITests.m 3 | // saveCoverUITests 4 | // 5 | // Created by 程召华 on 2017/10/30. 6 | // Copyright © 2017年 程召华. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface saveCoverUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation saveCoverUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // 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. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------