├── .gitignore ├── 1.JPG ├── 2.JPG ├── 3.JPG ├── 4.gif ├── LICENSE ├── README.md ├── RWFileTransferDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── RWFileTransferDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── rw_select.imageset │ │ ├── Contents.json │ │ └── ST_Common_select_2.png │ └── rw_unselect.imageset │ │ ├── Contents.json │ │ └── ST_Common_select_1.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Base │ ├── Controller │ │ ├── RWBaseViewController.h │ │ └── RWBaseViewController.m │ └── ViewModel │ │ ├── RWBaseViewModel.h │ │ └── RWBaseViewModel.m ├── Category │ ├── UIImage+Resize.h │ ├── UIImage+Resize.m │ ├── UIImage+Video.h │ └── UIImage+Video.m ├── Common │ ├── RWCommon.h │ └── RWCommon.m ├── Config │ ├── RWConfig.h │ ├── RWParamsConfig.h │ └── RWStringConfig.h ├── Info.plist ├── Model │ ├── RWAlbumModel.h │ └── RWAlbumModel.m ├── RWFileTransfer │ ├── RWBrower │ │ ├── RWBrowser.h │ │ └── RWBrowser.m │ ├── RWSessionManager │ │ ├── RWSession.h │ │ └── RWSession.m │ ├── RWStream │ │ ├── RWInputStream.h │ │ ├── RWInputStream.m │ │ ├── RWOutputStream.h │ │ ├── RWOutputStream.m │ │ ├── RWStream.h │ │ └── RWStream.m │ └── RWUserCenter │ │ ├── RWUserCenter.h │ │ └── RWUserCenter.m ├── RWHeader.pch ├── Section │ ├── File │ │ ├── ImageSelector │ │ │ ├── Controller │ │ │ │ ├── RWAlbumListViewController.h │ │ │ │ ├── RWAlbumListViewController.m │ │ │ │ ├── RWImageSelectorViewController.h │ │ │ │ └── RWImageSelectorViewController.m │ │ │ ├── Model │ │ │ │ ├── RWPhotoModel.h │ │ │ │ └── RWPhotoModel.m │ │ │ ├── View │ │ │ │ ├── RWAlbumListViewCell.h │ │ │ │ ├── RWAlbumListViewCell.m │ │ │ │ ├── RWAlbumListViewCell.xib │ │ │ │ ├── RWAlbumTableView.h │ │ │ │ ├── RWAlbumTableView.m │ │ │ │ ├── RWPhotoCollectView.h │ │ │ │ ├── RWPhotoCollectView.m │ │ │ │ ├── RWPhotoViewCell.h │ │ │ │ ├── RWPhotoViewCell.m │ │ │ │ └── RWPhotoViewCell.xib │ │ │ └── ViewModel │ │ │ │ ├── RWAlbumListViewModel.h │ │ │ │ ├── RWAlbumListViewModel.m │ │ │ │ ├── RWAlbumViewModel.h │ │ │ │ ├── RWAlbumViewModel.m │ │ │ │ ├── RWImageSelectorViewModel.h │ │ │ │ ├── RWImageSelectorViewModel.m │ │ │ │ ├── RWimageViewModel.h │ │ │ │ └── RWimageViewModel.m │ │ └── VideoSelector │ │ │ ├── Controller │ │ │ ├── RWVideoListViewController.h │ │ │ └── RWVideoListViewController.m │ │ │ └── View │ │ │ ├── RWVideoListViewCell.h │ │ │ ├── RWVideoListViewCell.m │ │ │ ├── RWVideoListViewCell.xib │ │ │ ├── RWVideoTableView.h │ │ │ └── RWVideoTableView.m │ ├── Search │ │ └── Controller │ │ │ ├── SearchViewController.h │ │ │ └── SearchViewController.m │ ├── Transfer │ │ ├── Controller │ │ │ ├── TransferListViewController.h │ │ │ └── TransferListViewController.m │ │ ├── View │ │ │ ├── RWTransferListCell.h │ │ │ ├── RWTransferListCell.m │ │ │ ├── RWTransferListView.h │ │ │ └── RWTransferListView.m │ │ └── ViewModel │ │ │ ├── RWTransferCenter.h │ │ │ ├── RWTransferCenter.m │ │ │ ├── RWTransferListViewModel.h │ │ │ ├── RWTransferListViewModel.m │ │ │ ├── RWTransferViewModel.h │ │ │ └── RWTransferViewModel.m │ └── Wait │ │ └── Controller │ │ ├── WaitViewController.h │ │ ├── WaitViewController.m │ │ └── WaitViewController.xib ├── Tool │ ├── RWDataTransfer │ │ ├── RWDataTransfer.h │ │ └── RWDataTransfer.m │ ├── RWFileHandle │ │ ├── RWFileHandle.h │ │ └── RWFileHandle.m │ ├── RWFileManager │ │ ├── RWFileManager.h │ │ └── RWFileManager.m │ └── RWImageLoad │ │ ├── RWImageLoad.h │ │ └── RWImageLoad.m ├── ViewController.h ├── ViewController.m └── main.m ├── RWFileTransferDemoTests ├── Info.plist └── RWFileTransferDemoTests.m └── RWFileTransferDemoUITests ├── Info.plist └── RWFileTransferDemoUITests.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryan-Wong-iOS/RWFileTransfer/18237a62dd01ee9de1f7730af6cd17ad7927ded7/1.JPG -------------------------------------------------------------------------------- /2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryan-Wong-iOS/RWFileTransfer/18237a62dd01ee9de1f7730af6cd17ad7927ded7/2.JPG -------------------------------------------------------------------------------- /3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryan-Wong-iOS/RWFileTransfer/18237a62dd01ee9de1f7730af6cd17ad7927ded7/3.JPG -------------------------------------------------------------------------------- /4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryan-Wong-iOS/RWFileTransfer/18237a62dd01ee9de1f7730af6cd17ad7927ded7/4.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ryan Wong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RWFileTransferDemo 2 | 3 | 本Demo主要是把公司项目中用到的文件传输技术整理出来,其中使用到iOS 7.0 后推出的Mutipeer Connectivity框架,借助WIFI、蓝牙来建立传输通道,达到用户互相传输媒体的功能 4 | 5 | ![image](https://github.com/Ryan-Wong-iOS/RWFileTransfer/blob/master/1.JPG) 6 | ![image](https://github.com/Ryan-Wong-iOS/RWFileTransfer/blob/master/2.JPG) 7 | ![image](https://github.com/Ryan-Wong-iOS/RWFileTransfer/blob/master/3.JPG) 8 | ![image](https://github.com/Ryan-Wong-iOS/RWFileTransfer/blob/master/4.gif) 9 | 10 | -------------------------------------------------------------------------------- /RWFileTransferDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RWFileTransferDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RWFileTransferDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. 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 | -------------------------------------------------------------------------------- /RWFileTransferDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. 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 | -------------------------------------------------------------------------------- /RWFileTransferDemo/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 | } -------------------------------------------------------------------------------- /RWFileTransferDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RWFileTransferDemo/Assets.xcassets/rw_select.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "ST_Common_select_2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RWFileTransferDemo/Assets.xcassets/rw_select.imageset/ST_Common_select_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryan-Wong-iOS/RWFileTransfer/18237a62dd01ee9de1f7730af6cd17ad7927ded7/RWFileTransferDemo/Assets.xcassets/rw_select.imageset/ST_Common_select_2.png -------------------------------------------------------------------------------- /RWFileTransferDemo/Assets.xcassets/rw_unselect.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "ST_Common_select_1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RWFileTransferDemo/Assets.xcassets/rw_unselect.imageset/ST_Common_select_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryan-Wong-iOS/RWFileTransfer/18237a62dd01ee9de1f7730af6cd17ad7927ded7/RWFileTransferDemo/Assets.xcassets/rw_unselect.imageset/ST_Common_select_1.png -------------------------------------------------------------------------------- /RWFileTransferDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 38 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Base/Controller/RWBaseViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWBaseViewController.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RWBaseViewController : UIViewController 12 | 13 | @property (strong, nonatomic, readonly)id baseViewModel; 14 | 15 | - (instancetype)initWithViewModel:(id)baseViewModel; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Base/Controller/RWBaseViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWBaseViewController.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWBaseViewController.h" 10 | 11 | @interface RWBaseViewController () 12 | 13 | @property (strong, nonatomic, readwrite)id baseViewModel; 14 | 15 | @end 16 | 17 | @implementation RWBaseViewController 18 | 19 | - (instancetype)initWithViewModel:(id)baseViewModel{ 20 | self = [super init]; 21 | if (self) { 22 | self.baseViewModel = baseViewModel; 23 | self.view.backgroundColor = [UIColor whiteColor]; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)viewDidLoad { 29 | [super viewDidLoad]; 30 | // Do any additional setup after loading the view. 31 | } 32 | 33 | - (void)didReceiveMemoryWarning { 34 | [super didReceiveMemoryWarning]; 35 | // Dispose of any resources that can be recreated. 36 | } 37 | 38 | /* 39 | #pragma mark - Navigation 40 | 41 | // In a storyboard-based application, you will often want to do a little preparation before navigation 42 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 43 | // Get the new view controller using [segue destinationViewController]. 44 | // Pass the selected object to the new view controller. 45 | } 46 | */ 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Base/ViewModel/RWBaseViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWBaseViewModel.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RWBaseViewModel : NSObject 12 | 13 | @property (copy, nonatomic)NSString *title; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Base/ViewModel/RWBaseViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWBaseViewModel.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWBaseViewModel.h" 10 | 11 | @implementation RWBaseViewModel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Category/UIImage+Resize.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Resize.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/4/5. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage(Resize) 12 | 13 | + (UIImage*)imageWithImageSimple:(UIImage*)image scaledToSize:(CGSize)newSize; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Category/UIImage+Resize.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Resize.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/4/5. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "UIImage+Resize.h" 10 | 11 | @implementation UIImage(Resize) 12 | 13 | + (UIImage*)imageWithImageSimple:(UIImage*)image scaledToSize:(CGSize)newSize 14 | { 15 | CGSize oldSize = image.size; 16 | CGFloat radio = oldSize.width / oldSize.height; 17 | CGSize finalSize; 18 | if (radio < 1.0) { 19 | finalSize = CGSizeMake(newSize.width, newSize.width / radio); 20 | } else { 21 | finalSize = CGSizeMake(newSize.height * radio, newSize.height); 22 | } 23 | 24 | UIGraphicsBeginImageContext(finalSize); 25 | [image drawInRect:CGRectMake(0,0,finalSize.width,finalSize.height)]; 26 | UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); 27 | UIGraphicsEndImageContext(); 28 | return newImage; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Category/UIImage+Video.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Video.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/4/5. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface UIImage (Video) 13 | 14 | + (UIImage *)getThumbnailImageWithFilePath:(NSString *)filePath; 15 | 16 | + (UIImage *)getThumbnailImageWithURLAsset:(AVURLAsset *)asset; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Category/UIImage+Video.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Video.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/4/5. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "UIImage+Video.h" 10 | 11 | @implementation UIImage (Video) 12 | 13 | + (UIImage *)getThumbnailImageWithFilePath:(NSString *)filePath { 14 | return [self getThumbnailImage:filePath]; 15 | } 16 | 17 | + (UIImage *)getThumbnailImageWithURLAsset:(AVURLAsset *)asset { 18 | return [self getThumbnailImage:asset]; 19 | } 20 | 21 | + (UIImage *)getThumbnailImage:(id)object 22 | { 23 | AVURLAsset *asset; 24 | if ([object isKindOfClass:[NSString class]]) { 25 | asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:object] options:nil]; 26 | } else if ([object isKindOfClass:[AVURLAsset class]]) { 27 | asset = (AVURLAsset *)object; 28 | } 29 | AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset]; 30 | gen.appliesPreferredTrackTransform = YES; 31 | CMTime time = CMTimeMakeWithSeconds(0.0, 600); 32 | NSError *error = nil; 33 | CMTime actualTime; 34 | CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error]; 35 | UIImage *thumb = [[UIImage alloc] initWithCGImage:image]; 36 | CGImageRelease(image); 37 | return thumb; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Common/RWCommon.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWCommon.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/31. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface RWCommon : NSObject 13 | 14 | +(NSString *)getFileSizeTextFromSize:(long long)fileSize; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Common/RWCommon.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWCommon.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/31. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWCommon.h" 10 | 11 | @implementation RWCommon 12 | 13 | +(NSString *)getFileSizeTextFromSize:(long long)fileSize { 14 | int sizeDig = 1024*1.0; 15 | 16 | const int GB = sizeDig * sizeDig * sizeDig;//定义GB的计算常量 17 | const int MB = sizeDig * sizeDig;//定义MB的计算常量 18 | const int KB = sizeDig;//定义KB的计算常量 19 | 20 | NSString *fileSizeText = @""; 21 | if (fileSize / GB >= 1) { 22 | fileSizeText = [NSString stringWithFormat:@"%.2fGB", (CGFloat)fileSize / (CGFloat)GB]; 23 | }else if (fileSize / MB >= 1){ 24 | fileSizeText = [NSString stringWithFormat:@"%.2fMB", (CGFloat)fileSize / (CGFloat)MB]; 25 | } else if (fileSize / KB >= 1){ 26 | fileSizeText = [NSString stringWithFormat:@"%.0fKB", (CGFloat)fileSize / (CGFloat)KB]; 27 | }else{ 28 | fileSizeText = [NSString stringWithFormat:@"%.0fB", (CGFloat)fileSize]; 29 | } 30 | 31 | return fileSizeText; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Config/RWConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWConfig.h 3 | // XKFM 4 | // 5 | // Created by RyanWong on 2017/10/14. 6 | // Copyright © 2017年 com.nest. All rights reserved. 7 | // 8 | 9 | #ifndef RWConfig_h 10 | #define RWConfig_h 11 | 12 | #define RWRGBA(r,g,b,a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a] 13 | 14 | #define RWHEXCOLOR(s) [UIColor colorWithRed:(((s & 0xFF0000) >> 16))/255.0 green:(((s &0xFF00) >>8))/255.0 blue:((s &0xFF))/255.0 alpha:1.0] 15 | 16 | #define RWHEXCOLORA(s,a) [UIColor colorWithRed:(((s & 0xFF0000) >> 16))/255.0 green:(((s &0xFF00) >>8))/255.0 blue:((s &0xFF))/255.0 alpha:a] 17 | 18 | #define kWidth [UIScreen mainScreen].bounds.size.width 19 | #define kHeight [UIScreen mainScreen].bounds.size.height 20 | #define kScale [UIScreen mainScreen].scale 21 | 22 | #define UI_IS_IPAD ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) 23 | #define UI_IS_IPHONE ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) 24 | #define UI_IS_IPHONE4 (UI_IS_IPHONE && [[UIScreen mainScreen] bounds].size.height < 568.0) 25 | #define UI_IS_IPHONE5 (UI_IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 568.0) 26 | #define UI_IS_IPHONE6 (UI_IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 667.0) 27 | #define UI_IS_IPHONE6PLUS (UI_IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 736.0 || [[UIScreen mainScreen] bounds].size.width == 736.0) // Both orientations 28 | #define UI_IS_IPHONEX (UI_IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0) 29 | 30 | /******************************************************************* 31 | * -- 本地化 32 | *******************************************************************/ 33 | #define Language(key) (NSString *)[[NSBundle mainBundle] localizedStringForKey:(key) value:key table:nil] 34 | #define LanguageWRV(key, value1) (NSString *)[Language(key) stringByReplacingOccurrencesOfString:@"[P0]" withString:value1] 35 | #define LanguageWRVS(key, value1, value2) (NSString *)[[Language(key) stringByReplacingOccurrencesOfString:@"[P0]" withString:value1] stringByReplacingOccurrencesOfString:@"[P1]" withString:value2] 36 | 37 | /******************************************************************* 38 | * -- 日志打印 39 | *******************************************************************/ 40 | #ifdef DEBUG 41 | # define RWLog(fmt, ...) NSLog((@"【日志】-- 方法:%s 行号:%d 日志内容:" fmt), __FUNCTION__, __LINE__, ##__VA_ARGS__); 42 | # define RWStatus(fmt, ...) NSLog((@"【状态】-- 日志内容:" fmt), ##__VA_ARGS__); 43 | # define RWError(fmt, ...) NSLog((@"【错误】-- 方法:%s 行号:%d 日志内容:" fmt), __FUNCTION__, __LINE__, ##__VA_ARGS__); 44 | #else 45 | # define RWLog(...); 46 | # define RWStatus(...); 47 | # define RWError(...); 48 | #endif 49 | 50 | #endif /* RWConfig_h */ 51 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Config/RWParamsConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWParamsConfig.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/4/5. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #ifndef RWParamsConfig_h 10 | #define RWParamsConfig_h 11 | 12 | #define CoverSize CGSizeMake(150, 150) 13 | 14 | #endif /* RWParamsConfig_h */ 15 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Config/RWStringConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWStringConfig.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #ifndef RWStringConfig_h 10 | #define RWStringConfig_h 11 | 12 | #import 13 | 14 | static NSString *const kFileTypePicture = @"picutre"; 15 | 16 | static NSString *const kFileTypeVideo = @"video"; 17 | 18 | typedef NS_ENUM(NSUInteger, RWTransferDataType) { 19 | RWTransferDataTypeSendTaskInfo, //发送方:发送任务基本信息 20 | RWTransferDataTypeReceiveTaskInfo, //接收方:反馈任务信息已接收 21 | RWTransferDataTypeReceiveProgress, //接收方:反馈给发送方实际接收进度 22 | RWTransferDataTypeCancel, //发送方、接收方:取消任务 23 | RWTransferDataTypeFinish, //接收方:完成接收 24 | RWTransferDataTypeError //发送方、接收方:发生错误 25 | }; 26 | 27 | #endif /* RWStringConfig_h */ 28 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSPhotoLibraryUsageDescription 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 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Model/RWAlbumModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbum.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/26. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface RWAlbumModel : NSObject 13 | 14 | @property (copy, nonatomic)NSString *title; 15 | 16 | @property (strong, nonatomic)PHFetchResult *result; 17 | 18 | @property (assign, nonatomic)NSInteger count; 19 | 20 | @property (assign, nonatomic)NSString *fileType; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Model/RWAlbumModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbum.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/26. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWAlbumModel.h" 10 | 11 | @implementation RWAlbumModel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWBrower/RWBrowser.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWBrower.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef void(^RWBrowserGetNearbyPeerBlock)(NSArray *peerArray); 13 | 14 | @interface RWBrowser : NSObject 15 | 16 | @property (copy, nonatomic)RWBrowserGetNearbyPeerBlock nearbyPeerBlock; 17 | 18 | + (instancetype)shareInstance; 19 | 20 | - (void)setConfigurationWithName:(NSString *)name Identifier:(NSString *)identifier; 21 | 22 | - (void)startSearchNearbyService; 23 | 24 | - (void)startWaitForConnect; 25 | 26 | - (void)stopSearch; 27 | 28 | - (void)stopWait; 29 | 30 | - (void)invitePeer:(MCPeerID *)peerId; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWBrower/RWBrowser.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWBrower.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWBrowser.h" 10 | #import 11 | #import "RWSession.h" 12 | #import "RWUserCenter.h" 13 | 14 | @interface RWBrowser() 15 | 16 | @property (strong, nonatomic)RWUserCenter *userCenter; 17 | 18 | @property (strong, nonatomic)MCNearbyServiceAdvertiser *advertiser; 19 | 20 | @property (strong, nonatomic)MCNearbyServiceBrowser *browser; 21 | 22 | @property (strong, nonatomic)NSMutableArray *peerArray; 23 | 24 | @end 25 | 26 | static RWBrowser *_instance = nil; 27 | @implementation RWBrowser 28 | 29 | + (instancetype)shareInstance { 30 | static dispatch_once_t onceToken; 31 | dispatch_once(&onceToken, ^{ 32 | _instance = [[RWBrowser alloc] init]; 33 | }); 34 | return _instance; 35 | } 36 | 37 | - (void)setConfigurationWithName:(NSString *)name Identifier:(NSString *)identifier { 38 | _userCenter = [RWUserCenter center]; 39 | _userCenter.name = name; 40 | _userCenter.identifier = identifier; 41 | _userCenter.myPeerID = [[MCPeerID alloc] initWithDisplayName:name]; 42 | _userCenter.session = [[RWSession alloc] initWithPeer:_userCenter.myPeerID]; 43 | } 44 | 45 | - (void)startSearchNearbyService { 46 | [self.peerArray removeAllObjects]; 47 | 48 | _browser = [[MCNearbyServiceBrowser alloc] initWithPeer:_userCenter.myPeerID serviceType:_userCenter.identifier]; 49 | _browser.delegate = self; 50 | [_browser startBrowsingForPeers]; 51 | } 52 | 53 | - (void)startWaitForConnect { 54 | _advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:_userCenter.myPeerID discoveryInfo:nil serviceType:_userCenter.identifier]; 55 | _advertiser.delegate = self; 56 | [_advertiser startAdvertisingPeer]; 57 | } 58 | 59 | - (void)stopSearch { 60 | [_browser stopBrowsingForPeers]; 61 | } 62 | 63 | - (void)stopWait { 64 | [_advertiser stopAdvertisingPeer]; 65 | } 66 | 67 | - (void)invitePeer:(MCPeerID *)peerId { 68 | if ([_userCenter.session.session.connectedPeers containsObject:peerId]) { 69 | NSLog(@"与%@已经连接了,无须再次连接",peerId.displayName); 70 | return; 71 | } 72 | [_browser invitePeer:peerId toSession:_userCenter.session.session withContext:nil timeout:15]; 73 | } 74 | 75 | #pragma mark - Browser Delegate 76 | -(void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info { 77 | NSLog(@"发现附近用户%@",peerID.displayName); 78 | 79 | [self checkPeerArray:peerID]; 80 | 81 | [self.peerArray addObject:peerID]; 82 | 83 | if (_nearbyPeerBlock) { 84 | _nearbyPeerBlock((NSArray *)_peerArray); 85 | } 86 | } 87 | 88 | -(void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID { 89 | NSLog(@"丢失用户%@",peerID.displayName); 90 | 91 | [self checkPeerArray:peerID]; 92 | 93 | if (_nearbyPeerBlock) { 94 | _nearbyPeerBlock((NSArray *)_peerArray); 95 | } 96 | } 97 | 98 | - (void)checkPeerArray:(MCPeerID *)peerID { 99 | NSMutableArray *existArray = [NSMutableArray arrayWithCapacity:_peerArray.count]; 100 | for (MCPeerID *tempPeerId in _peerArray) { 101 | if ([tempPeerId.displayName isEqualToString:peerID.displayName]) { 102 | [existArray addObject:tempPeerId]; 103 | } 104 | } 105 | if (existArray.count > 0) { 106 | [_peerArray removeObjectsInArray:existArray]; 107 | } 108 | } 109 | 110 | #pragma mark - MCNearbyServiceAdvertiserDelegate 111 | - (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(nullable NSData *)context invitationHandler:(void (^)(BOOL accept, MCSession * __nullable session))invitationHandler { 112 | invitationHandler(YES, _userCenter.session.session); 113 | } 114 | 115 | #pragma mark - Lazy load 116 | - (NSMutableArray *)peerArray { 117 | if (!_peerArray) { 118 | _peerArray = [NSMutableArray array]; 119 | } 120 | return _peerArray; 121 | } 122 | @end 123 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWSessionManager/RWSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWSession.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | static NSString *const kRWSessionStateConnectedNotification = @"RWSessionStateConnectedNotification"; 13 | static NSString *const kRWSessionStateNotConnectedNotification = @"RWSessionStateNotConnectedNotification"; 14 | 15 | @class RWSession; 16 | @protocol RWSessionDelegate 17 | 18 | - (void)session:(RWSession *)session didReceiveStream:(NSInputStream *)stream WithName:(NSString *)streamName; 19 | - (void)session:(RWSession *)session didReceiveData:(NSData *)data; 20 | 21 | @end 22 | 23 | @interface RWSession : NSObject 24 | 25 | @property (strong, nonatomic)MCSession *session; 26 | 27 | @property (assign, nonatomic)id delegate; 28 | 29 | - (instancetype)initWithPeer:(MCPeerID *)peerId; 30 | 31 | + (void)kickPeer:(MCPeerID *)peerId; 32 | 33 | - (NSArray *)connectedPeers; 34 | 35 | - (NSOutputStream *)outputStreamForPeer:(MCPeerID *)peer With:(NSString *)name; 36 | 37 | - (BOOL)sendData:(NSData *)data toPeers:(NSArray *)peers; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWSessionManager/RWSession.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWSessionManager.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWSession.h" 10 | #import "RWUserCenter.h" 11 | 12 | @interface RWSession() 13 | 14 | @end 15 | 16 | @implementation RWSession 17 | 18 | - (instancetype)initWithPeer:(MCPeerID *)peerId { 19 | self = [super init]; 20 | if (self) { 21 | _session = [[MCSession alloc] initWithPeer:peerId securityIdentity:nil encryptionPreference:MCEncryptionNone]; 22 | _session.delegate = self; 23 | } 24 | return self; 25 | } 26 | 27 | + (void)kickPeer:(MCPeerID *)peerId { 28 | [[RWUserCenter center].session.session cancelConnectPeer:peerId]; 29 | } 30 | 31 | - (NSArray *)connectedPeers { 32 | return [self.session connectedPeers]; 33 | } 34 | 35 | - (NSOutputStream *)outputStreamForPeer:(MCPeerID *)peer With:(NSString *)name { 36 | NSError *error; 37 | NSOutputStream *outputStream = [self.session startStreamWithName:name toPeer:peer error:&error]; 38 | if (error) { 39 | RWError(@"%@", [error userInfo].description); 40 | } 41 | return outputStream; 42 | } 43 | 44 | - (BOOL)sendData:(NSData *)data toPeers:(NSArray *)peers { 45 | NSError *error; 46 | BOOL result = [self.session sendData:data toPeers:peers withMode:MCSessionSendDataReliable error:&error]; 47 | if (error) { 48 | RWError(@"%@", [error userInfo].description); 49 | } 50 | return result; 51 | } 52 | 53 | #pragma mark - MCSession Delegate 54 | -(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state { 55 | if (state == MCSessionStateConnected) { 56 | RWStatus(@"与%@连接上",peerID.displayName); 57 | self.session = session; 58 | 59 | [[NSNotificationCenter defaultCenter] postNotificationName:kRWSessionStateConnectedNotification object:nil]; 60 | } else if (state == MCSessionStateConnecting) { 61 | RWStatus(@"与%@连接中",peerID.displayName); 62 | } else if (state == MCSessionStateNotConnected) { 63 | RWStatus(@"与%@连接断开",peerID.displayName); 64 | self.session = nil; 65 | 66 | [[NSNotificationCenter defaultCenter] postNotificationName:kRWSessionStateNotConnectedNotification object:nil]; 67 | } 68 | } 69 | 70 | -(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID { 71 | if (_delegate && [_delegate respondsToSelector:@selector(session:didReceiveData:)]) { 72 | [_delegate session:self didReceiveData:data]; 73 | } 74 | } 75 | 76 | -(void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID { 77 | if (_delegate && [_delegate respondsToSelector:@selector(session:didReceiveStream:WithName:)]) { 78 | [_delegate session:self didReceiveStream:stream WithName:streamName]; 79 | } 80 | } 81 | 82 | // Start receiving a resource from remote peer. 83 | - (void) session:(MCSession *)session 84 | didStartReceivingResourceWithName:(NSString *)resourceName 85 | fromPeer:(MCPeerID *)peerID 86 | withProgress:(NSProgress *)progress{} 87 | 88 | // Finished receiving a resource from remote peer and saved the content 89 | // in a temporary location - the app is responsible for moving the file 90 | // to a permanent location within its sandbox. 91 | - (void) session:(MCSession *)session 92 | didFinishReceivingResourceWithName:(NSString *)resourceName 93 | fromPeer:(MCPeerID *)peerID 94 | atURL:(nullable NSURL *)localURL 95 | withError:(nullable NSError *)error{} 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWStream/RWInputStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWInputStream.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/3. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RWInputStream; 12 | @protocol RWInputStreamDelegate 13 | 14 | - (void)inputStream:(RWInputStream *)inputStream streamName:(NSString *)streamName progress:(long long)progress; 15 | 16 | - (void)inputStream:(RWInputStream *)inputStream transferEndWithStreamName:(NSString *)streamName filePath:(NSString *)filePath; 17 | 18 | - (void)inputStream:(RWInputStream *)inputStream transferErrorWithStreamName:(NSString *)streamName; 19 | 20 | @end 21 | 22 | @interface RWInputStream : NSObject 23 | 24 | @property (copy, nonatomic)NSString *streamName; 25 | 26 | @property (assign, nonatomic)id delegate; 27 | 28 | - (instancetype)initWithInputStream:(NSInputStream *)inputStream; 29 | 30 | - (void)start; 31 | 32 | - (void)stop; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWStream/RWInputStream.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWInputStream.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/3. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWInputStream.h" 10 | #import "RWStream.h" 11 | #import "RWFileManager.h" 12 | 13 | UInt32 const kRWStreamReadMaxLength = 4096; 14 | 15 | @interface RWInputStream() 16 | 17 | @property (strong, nonatomic)RWStream *stream; 18 | 19 | @property (strong, nonatomic)NSThread *streamThread; 20 | 21 | @property (strong, nonatomic)NSMutableData *appendData; 22 | 23 | @property (strong, nonatomic)NSFileHandle *fileHandle; 24 | 25 | @property (assign, nonatomic)long long progress; 26 | 27 | @end 28 | 29 | @implementation RWInputStream 30 | 31 | - (instancetype)initWithInputStream:(NSInputStream *)inputStream { 32 | self = [super init]; 33 | if (self) { 34 | self.stream = [[RWStream alloc] initWithInputStream:inputStream]; 35 | self.stream.delegate = self; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)start { 41 | if (![[NSThread currentThread] isMainThread]) { 42 | return [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES]; 43 | } 44 | 45 | _progress = 0; 46 | 47 | self.streamThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; 48 | [self.streamThread start]; 49 | } 50 | 51 | - (void)run { 52 | @autoreleasepool { 53 | [self.stream open]; 54 | 55 | while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); 56 | } 57 | } 58 | 59 | - (void)stop 60 | { 61 | [self performSelector:@selector(stopThread) onThread:self.streamThread withObject:nil waitUntilDone:YES]; 62 | } 63 | 64 | - (void)stopThread 65 | { 66 | [self.stream close]; 67 | self.appendData = nil; 68 | [self.fileHandle synchronizeFile]; 69 | [self.fileHandle closeFile]; 70 | self.fileHandle = nil; 71 | [self.streamThread cancel]; 72 | self.stream = nil; 73 | self.delegate = nil; 74 | RWStatus(@"Stop"); 75 | } 76 | 77 | #pragma mark - RWStreamDelegate 78 | - (void)rwStream:(RWStream *)stream handleEvent:(RWStreamEvent)event { 79 | switch (event) { 80 | case RWStreamEventHasData: { 81 | @autoreleasepool { 82 | uint8_t bytes[kRWStreamReadMaxLength]; 83 | UInt32 length = [stream readData:bytes maxLength:kRWStreamReadMaxLength]; 84 | NSData *data = [NSData dataWithBytes:(const void *)bytes length:length]; 85 | [self.fileHandle seekToEndOfFile]; 86 | [self.fileHandle writeData:data]; 87 | _progress += length; 88 | 89 | if (_delegate && [_delegate respondsToSelector:@selector(inputStream:streamName:progress:)]) { 90 | [_delegate inputStream:self streamName:_streamName progress:_progress]; 91 | } 92 | RWLog(@"Transfering %d / %lld", (unsigned int)length, _progress); 93 | } 94 | break; 95 | } 96 | 97 | case RWStreamEventEnd: 98 | RWStatus(@"Transfer End"); 99 | if (_delegate && [_delegate respondsToSelector:@selector(inputStream:transferEndWithStreamName:filePath:)]) { 100 | [_delegate inputStream:self transferEndWithStreamName:_streamName filePath:[self getTmpFilePath]]; 101 | } 102 | break; 103 | 104 | case RWStreamEventError: 105 | RWStatus(@"Transfer Error"); 106 | if (_delegate && [_delegate respondsToSelector:@selector(inputStream:transferErrorWithStreamName:)]) { 107 | [_delegate inputStream:self transferErrorWithStreamName:_streamName]; 108 | } 109 | break; 110 | 111 | default: 112 | break; 113 | } 114 | } 115 | 116 | #pragma mark - Lazy load 117 | 118 | -(NSMutableData *)appendData { 119 | if (!_appendData) { 120 | _appendData = [NSMutableData data]; 121 | } 122 | return _appendData; 123 | } 124 | 125 | -(NSFileHandle *)fileHandle { 126 | if (!_fileHandle) { 127 | NSString *path = [self getTmpFilePath]; 128 | [RWFileManager createBlankFileAtPath:path]; 129 | _fileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; 130 | } 131 | return _fileHandle; 132 | } 133 | 134 | - (NSString *)getTmpFilePath { 135 | NSString *tmp = [RWFileManager tmpPath]; 136 | NSString *path = [NSString stringWithFormat:@"%@%@", tmp, _streamName]; 137 | return path; 138 | } 139 | 140 | -(void)dealloc { 141 | RWStatus(@"RWInputStream 销毁"); 142 | } 143 | 144 | @end 145 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWStream/RWOutputStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWOutputStream.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/3. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RWOutputStream; 12 | @protocol RWOutputStreamDelegate 13 | 14 | - (void)outputStream:(RWOutputStream *)outputStream progress:(long long)progress; 15 | 16 | - (void)outputStream:(RWOutputStream *)outputStream transferEndWithStreamName:(NSString *)name; 17 | 18 | - (void)outputStream:(RWOutputStream *)outputStream transferErrorWithStreamName:(NSString *)name; 19 | 20 | @end 21 | 22 | @interface RWOutputStream : NSObject 23 | 24 | @property (copy, nonatomic)NSString *streamName; 25 | 26 | @property (assign, nonatomic)id delegate; 27 | 28 | - (instancetype)initWithOutputStream:(NSOutputStream *)outputStream; 29 | 30 | - (void)streamWithAsset:(id)asset fileType:(NSString *)fileType; 31 | 32 | - (void)start; 33 | 34 | - (void)stop; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWStream/RWOutputStream.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWOutputStream.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/3. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWOutputStream.h" 10 | #import "RWStream.h" 11 | 12 | #import "RWImageLoad.h" 13 | 14 | UInt32 const kRWStreamWriteMaxLength = 4096; 15 | 16 | @interface RWOutputStream() 17 | 18 | @property (strong, nonatomic)RWStream *stream; 19 | 20 | @property (strong, nonatomic)NSOutputStream *outputStream; 21 | 22 | @property (strong, nonatomic)NSThread *streamThread; 23 | 24 | @property (strong, nonatomic)NSData *imageData; 25 | 26 | @property (assign, nonatomic)long long sendSize; 27 | 28 | @property (assign, nonatomic)long long totalSize; 29 | 30 | @property (assign, nonatomic)BOOL readyForSend; 31 | 32 | @end 33 | 34 | @implementation RWOutputStream 35 | 36 | - (instancetype)initWithOutputStream:(NSOutputStream *)outputStream { 37 | self = [super init]; 38 | if (self) { 39 | RWStatus(@"Init"); 40 | self.stream = [[RWStream alloc] initWithOutputStream:outputStream]; 41 | self.stream.delegate = self; 42 | self.readyForSend = NO; 43 | self.outputStream = outputStream; 44 | } 45 | return self; 46 | } 47 | 48 | - (void)start { 49 | if (![[NSThread currentThread] isMainThread]) { 50 | return [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES]; 51 | } 52 | 53 | RWStatus(@"Start"); 54 | self.streamThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; 55 | [self.streamThread start]; 56 | } 57 | 58 | - (void)run { 59 | @autoreleasepool { 60 | [self.stream open]; 61 | 62 | RWStatus(@"Loop"); 63 | 64 | // while (self.readyForSend && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); 65 | while (!_readyForSend) { 66 | NSLog(@"等待视频获取"); 67 | } 68 | while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { 69 | } 70 | 71 | RWStatus(@"Done"); 72 | } 73 | } 74 | 75 | - (void)stop 76 | { 77 | [self performSelector:@selector(stopThread) onThread:self.streamThread withObject:nil waitUntilDone:YES]; 78 | } 79 | 80 | - (void)stopThread 81 | { 82 | self.readyForSend = NO; 83 | [self.stream close]; 84 | self.stream = nil; 85 | self.outputStream = nil; 86 | self.imageData = nil; 87 | [self.streamThread cancel]; 88 | RWStatus(@"Stop"); 89 | } 90 | 91 | - (void)streamWithAsset:(id)asset fileType:(NSString *)fileType { 92 | __weak typeof(self)weakSelf = self; 93 | 94 | if (fileType == kFileTypePicture) { 95 | [[RWImageLoad shareLoad] getPhotoDataWithAsset:asset completion:^(NSData *imageData, NSString *dataUTI, NSDictionary *info) { 96 | weakSelf.imageData = [NSData dataWithData:imageData]; 97 | weakSelf.totalSize = weakSelf.imageData.length; 98 | weakSelf.sendSize = 0; 99 | weakSelf.readyForSend = YES; 100 | 101 | RWLog(@"成功获取图片数据"); 102 | }]; 103 | } else if (fileType == kFileTypeVideo) { 104 | [[RWImageLoad shareLoad] getVideoDataWithAsset:asset completion:^(NSData *data) { 105 | weakSelf.imageData = [NSData dataWithData:data]; 106 | weakSelf.totalSize = weakSelf.imageData.length; 107 | weakSelf.sendSize = 0; 108 | weakSelf.readyForSend = YES; 109 | 110 | RWLog(@"成功获取视频数据"); 111 | }]; 112 | } 113 | } 114 | 115 | - (void)sendDataChunk { 116 | @autoreleasepool { 117 | NSMutableData *data = [NSMutableData dataWithData:_imageData]; 118 | uint8_t *readBytes = (uint8_t *)[data mutableBytes]; 119 | readBytes += _sendSize; 120 | NSUInteger data_len = [data length]; 121 | unsigned long long len = (data_len - _sendSize >= kRWStreamWriteMaxLength) ? kRWStreamWriteMaxLength : (data_len - _sendSize); 122 | uint8_t buf[len]; 123 | (void)memcpy(buf, readBytes, len); 124 | len = [self.stream writeData:(const uint8_t *)buf maxLength:(UInt32)len]; 125 | _sendSize += len; 126 | RWLog(@"Sending : %u", (unsigned int)len); 127 | RWLog(@"Sending progress : %u / %u", (unsigned int)_sendSize, (unsigned int)_totalSize); 128 | } 129 | } 130 | 131 | #pragma mark - RWStreamDelegate 132 | - (void)rwStream:(RWStream *)stream handleEvent:(RWStreamEvent)event { 133 | switch (event) { 134 | case RWStreamEventHasSpace:{ 135 | RWStatus(@"Sending"); 136 | [self sendDataChunk]; 137 | break; 138 | } 139 | 140 | case RWStreamEventEnd:{ 141 | RWStatus(@"Send End"); 142 | if (_delegate && [_delegate respondsToSelector:@selector(outputStream:transferEndWithStreamName:)]) { 143 | [_delegate outputStream:self transferEndWithStreamName:_streamName]; 144 | } 145 | [self stop]; 146 | break; 147 | } 148 | 149 | case RWStreamEventError:{ 150 | RWStatus(@"Send Error"); 151 | if (_delegate && [_delegate respondsToSelector:@selector(outputStream:transferErrorWithStreamName:)]) { 152 | [_delegate outputStream:self transferErrorWithStreamName:_streamName]; 153 | } 154 | [self stop]; 155 | break; 156 | } 157 | 158 | default: 159 | break; 160 | } 161 | } 162 | 163 | -(void)dealloc { 164 | RWStatus(@"RWOutputStream 销毁") 165 | } 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWStream/RWStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWStream.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/3. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSUInteger, RWStreamEvent) { 12 | RWStreamEventHasData, 13 | RWStreamEventHasSpace, 14 | RWStreamEventError, 15 | RWStreamEventEnd, 16 | }; 17 | 18 | @class RWStream; 19 | @protocol RWStreamDelegate 20 | 21 | - (void)rwStream:(RWStream *)stream handleEvent:(RWStreamEvent)event; 22 | 23 | @end 24 | 25 | @interface RWStream : NSObject 26 | 27 | @property (strong, nonatomic)NSStream *stream; 28 | 29 | @property (assign, nonatomic) id delegate; 30 | 31 | - (instancetype)initWithInputStream:(NSInputStream *)inputStream; 32 | - (instancetype)initWithOutputStream:(NSOutputStream *)outputStream; 33 | 34 | - (void)open; 35 | - (void)close; 36 | - (UInt32)readData:(uint8_t *)data maxLength:(UInt32)maxLength; 37 | - (UInt32)writeData:(const uint8_t *)data maxLength:(UInt32)maxLength; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWStream/RWStream.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWStream.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/3. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWStream.h" 10 | 11 | @interface RWStream() 12 | 13 | @end 14 | 15 | @implementation RWStream 16 | 17 | - (instancetype) initWithInputStream:(NSInputStream *)inputStream { 18 | self = [super init]; 19 | if (self) { 20 | self.stream = inputStream; 21 | } 22 | return self; 23 | } 24 | 25 | - (instancetype) initWithOutputStream:(NSOutputStream *)outputStream { 26 | self = [super init]; 27 | if (self) { 28 | self.stream = outputStream; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)open { 34 | self.stream.delegate = self; 35 | [self.stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 36 | [self.stream open]; 37 | } 38 | 39 | - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { 40 | switch (eventCode) { 41 | case NSStreamEventHasBytesAvailable: 42 | if (_delegate && [_delegate respondsToSelector:@selector(rwStream:handleEvent:)]) { 43 | [_delegate rwStream:self handleEvent:RWStreamEventHasData]; 44 | } 45 | break; 46 | 47 | case NSStreamEventHasSpaceAvailable: 48 | if (_delegate && [_delegate respondsToSelector:@selector(rwStream:handleEvent:)]) { 49 | [_delegate rwStream:self handleEvent:RWStreamEventHasSpace]; 50 | } 51 | break; 52 | 53 | case NSStreamEventErrorOccurred: 54 | if (_delegate && [_delegate respondsToSelector:@selector(rwStream:handleEvent:)]) { 55 | [_delegate rwStream:self handleEvent:RWStreamEventError]; 56 | } 57 | break; 58 | 59 | case NSStreamEventEndEncountered: 60 | if (_delegate && [_delegate respondsToSelector:@selector(rwStream:handleEvent:)]) { 61 | [_delegate rwStream:self handleEvent:RWStreamEventEnd]; 62 | } 63 | break; 64 | 65 | default: 66 | { 67 | // RWStatus(@"Send Other Status %lu", (unsigned long)eventCode); 68 | } 69 | break; 70 | } 71 | } 72 | 73 | - (UInt32)readData:(uint8_t *)data maxLength:(UInt32)maxLength { 74 | return (UInt32)[(NSInputStream *)self.stream read:data maxLength:maxLength]; 75 | } 76 | 77 | - (UInt32)writeData:(const uint8_t *)data maxLength:(UInt32)maxLength { 78 | return (UInt32)[(NSOutputStream *)self.stream write:data maxLength:maxLength]; 79 | } 80 | 81 | - (void)close { 82 | [self.stream close]; 83 | self.stream.delegate = nil; 84 | [self.stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 85 | } 86 | 87 | - (void)dealloc { 88 | RWStatus(@"Stream 销毁"); 89 | // if (self.stream) { 90 | // [self close]; 91 | // } 92 | // if (self.delegate) { 93 | // self.delegate = nil; 94 | // } 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWUserCenter/RWUserCenter.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWUserCenter.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "RWSession.h" 12 | 13 | @interface RWUserCenter : NSObject 14 | 15 | @property (strong, nonatomic)NSString *name; 16 | 17 | @property (strong, nonatomic)NSString *identifier; 18 | 19 | @property (strong, nonatomic)MCPeerID *myPeerID; 20 | 21 | @property (strong, nonatomic)RWSession *session; 22 | 23 | + (instancetype)center; 24 | 25 | - (void)remove; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWFileTransfer/RWUserCenter/RWUserCenter.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWUserCenter.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWUserCenter.h" 10 | 11 | static RWUserCenter *_instance = nil; 12 | @implementation RWUserCenter 13 | 14 | + (instancetype)center { 15 | static dispatch_once_t onceToken; 16 | dispatch_once(&onceToken, ^{ 17 | _instance = [[RWUserCenter alloc] init]; 18 | }); 19 | return _instance; 20 | } 21 | 22 | - (void)remove { 23 | self.name = nil; 24 | self.identifier = nil; 25 | self.myPeerID = nil; 26 | self.session = nil; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /RWFileTransferDemo/RWHeader.pch: -------------------------------------------------------------------------------- 1 | // 2 | // RWHeader.pch 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #ifndef RWHeader_pch 10 | #define RWHeader_pch 11 | 12 | // Include any system framework and library headers here that should be included in all compilation units. 13 | // You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. 14 | 15 | #import "RWConfig.h" 16 | #import "RWStringConfig.h" 17 | #import "RWParamsConfig.h" 18 | #import "RWCommon.h" 19 | 20 | #endif /* RWHeader_pch */ 21 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/Controller/RWAlbumListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumListViewController.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RWBaseViewController.h" 11 | #import "RWAlbumListViewModel.h" 12 | 13 | @interface RWAlbumListViewController : RWBaseViewController 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/Controller/RWAlbumListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumListViewController.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWAlbumListViewController.h" 10 | #import "RWAlbumTableView.h" 11 | 12 | @interface RWAlbumListViewController () 13 | 14 | @property (strong, nonatomic)RWAlbumListViewModel *viewModel; 15 | 16 | @property (strong, nonatomic)RWAlbumTableView *albumTv; 17 | 18 | @property (strong, nonatomic)UIButton *sendBtn; 19 | 20 | @end 21 | 22 | @implementation RWAlbumListViewController 23 | 24 | - (void)viewWillAppear:(BOOL)animated { 25 | [super viewWillAppear:animated]; 26 | [_albumTv reloadData]; 27 | } 28 | 29 | - (void)viewDidLoad { 30 | [super viewDidLoad]; 31 | 32 | self.viewModel = (RWAlbumListViewModel *)self.baseViewModel; 33 | 34 | self.title = self.viewModel.title; 35 | 36 | [self.view addSubview:self.albumTv]; 37 | 38 | [self.view addSubview:self.sendBtn]; 39 | 40 | [self.viewModel loadAlbumDataContentType:RWAlbumListContentTypePhoto success:^(id responseObject) { 41 | [self.albumTv reloadWithData:self.viewModel.albums]; 42 | } failure:^(NSError *error) { 43 | 44 | }]; 45 | } 46 | 47 | - (void)sendAction { 48 | [self.viewModel submitAllTransferDatas]; 49 | [self.navigationController popViewControllerAnimated:YES]; 50 | } 51 | 52 | - (void)didReceiveMemoryWarning { 53 | [super didReceiveMemoryWarning]; 54 | // Dispose of any resources that can be recreated. 55 | } 56 | 57 | -(RWAlbumTableView *)albumTv { 58 | if (!_albumTv) { 59 | _albumTv = [[RWAlbumTableView alloc] initWithViewController:self frame:self.view.frame]; 60 | } 61 | return _albumTv; 62 | } 63 | 64 | -(UIButton *)sendBtn { 65 | if (!_sendBtn) { 66 | _sendBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 50, self.view.frame.size.width, 50)]; 67 | [_sendBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 68 | [_sendBtn setTitle:@"发送" forState:UIControlStateNormal]; 69 | [_sendBtn addTarget:self action:@selector(sendAction) forControlEvents:UIControlEventTouchUpInside]; 70 | } 71 | return _sendBtn; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/Controller/RWImageSelectorViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWImageSelectorViewController.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/26. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RWBaseViewController.h" 11 | 12 | @interface RWImageSelectorViewController : RWBaseViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/Controller/RWImageSelectorViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWImageSelectorViewController.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/26. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWImageSelectorViewController.h" 10 | #import "RWImageSelectorViewModel.h" 11 | #import "RWAlbumViewModel.h" 12 | #import "RWimageViewModel.h" 13 | #import "RWPhotoCollectView.h" 14 | 15 | #import "RWImageLoad.h" 16 | 17 | @interface RWImageSelectorViewController () 18 | 19 | @property (strong, nonatomic)RWPhotoCollectView *photoCV; 20 | 21 | @property (strong, nonatomic)RWImageSelectorViewModel *viewModel; 22 | 23 | @end 24 | 25 | @implementation RWImageSelectorViewController 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | 30 | self.viewModel = self.baseViewModel; 31 | self.title = self.viewModel.title; 32 | 33 | [self.view addSubview:self.photoCV]; 34 | 35 | [_photoCV reloadWithData:_viewModel.albumViewModel]; 36 | } 37 | 38 | - (void)didReceiveMemoryWarning { 39 | [super didReceiveMemoryWarning]; 40 | // Dispose of any resources that can be recreated. 41 | } 42 | 43 | - (RWPhotoCollectView *)photoCV { 44 | if (!_photoCV) { 45 | _photoCV = [[RWPhotoCollectView alloc] initWithFrame:self.view.frame]; 46 | } 47 | return _photoCV; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/Model/RWPhotoModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWPhotoModel.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class PHAsset; 12 | @interface RWPhotoModel : NSObject 13 | 14 | @property (copy, nonatomic)NSString *name; 15 | 16 | @property (copy, nonatomic)PHAsset *asset; 17 | 18 | @property (assign, nonatomic)long long size; 19 | 20 | @property (copy, nonatomic)NSString *fileType; 21 | 22 | @property (copy, nonatomic)NSString *pathExtension; 23 | 24 | - (instancetype)initWithDictionary:(NSDictionary *)dict; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/Model/RWPhotoModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWPhotoModel.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWPhotoModel.h" 10 | #import 11 | @implementation RWPhotoModel 12 | 13 | - (instancetype)initWithDictionary:(NSDictionary *)dict { 14 | self = [super init]; 15 | if (self) { 16 | _name = dict[@"name"]; 17 | _size = [dict[@"size"] longLongValue]; 18 | _fileType = dict[@"fileType"]; 19 | _pathExtension = dict[@"pathExtension"]; 20 | } 21 | return self; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWAlbumListViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumListViewCell.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RWAlbumViewModel; 12 | @interface RWAlbumListViewCell : UITableViewCell 13 | 14 | - (void)bindViewModel:(RWAlbumViewModel *)viewModel; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWAlbumListViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumListViewCell.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWAlbumListViewCell.h" 10 | #import "RWAlbumViewModel.h" 11 | 12 | @interface RWAlbumListViewCell() 13 | 14 | @property (weak, nonatomic) IBOutlet UIButton *selectBtn; 15 | @property (weak, nonatomic) IBOutlet UILabel *titleLab; 16 | @property (weak, nonatomic) IBOutlet UILabel *countLab; 17 | 18 | @property (strong, nonatomic) RWAlbumViewModel *viewModel; 19 | 20 | @end 21 | 22 | @implementation RWAlbumListViewCell 23 | 24 | - (void)awakeFromNib { 25 | [super awakeFromNib]; 26 | // Initialization code 27 | } 28 | 29 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 30 | [super setSelected:selected animated:animated]; 31 | 32 | // Configure the view for the selected state 33 | } 34 | 35 | - (void)bindViewModel:(RWAlbumViewModel *)viewModel { 36 | _viewModel = viewModel; 37 | 38 | _titleLab.text = _viewModel.title; 39 | _countLab.text = [NSString stringWithFormat:@"%zd/%zd", _viewModel.selectedCount, _viewModel.count]; 40 | _selectBtn.selected = _viewModel.selected; 41 | } 42 | 43 | - (IBAction)selectAction:(id)sender { 44 | UIButton *button = (UIButton *)sender; 45 | if (!button.selected) { 46 | [_viewModel selectedAll]; 47 | } else { 48 | [_viewModel removeAll]; 49 | } 50 | button.selected = !button.selected; 51 | _countLab.text = [NSString stringWithFormat:@"%zd/%zd", _viewModel.selectedCount, _viewModel.count]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWAlbumListViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 33 | 39 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWAlbumTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumTableView.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/27. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RWAlbumTableView : UIView 12 | 13 | - (instancetype) initWithViewController:(UIViewController *)vc frame:(CGRect)frame; 14 | 15 | - (void)reloadData; 16 | 17 | - (void)reloadWithData:(NSArray *)data; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWAlbumTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumTableView.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/27. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWAlbumTableView.h" 10 | #import "RWAlbumModel.h" 11 | #import "RWAlbumViewModel.h" 12 | #import "RWAlbumListViewCell.h" 13 | #import "RWImageSelectorViewModel.h" 14 | 15 | #import "RWImageSelectorViewController.h" 16 | 17 | @interface RWAlbumTableView() 18 | 19 | @property (strong, nonatomic)UITableView *tv; 20 | 21 | @property (strong, nonatomic)NSMutableArray *albums; 22 | 23 | @property (strong, nonatomic)UIViewController *parentViewController; 24 | 25 | @end 26 | 27 | @implementation RWAlbumTableView 28 | 29 | - (instancetype) initWithViewController:(UIViewController *)vc frame:(CGRect)frame { 30 | self = [super initWithFrame:frame]; 31 | if (self) { 32 | _parentViewController = vc; 33 | [self setupView]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)setupView { 39 | [self addSubview:self.tv]; 40 | } 41 | 42 | #pragma mark - TableView delegate datasource 43 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 44 | return self.albums.count; 45 | } 46 | 47 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 48 | RWAlbumListViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([RWAlbumListViewCell class]) forIndexPath:indexPath]; 49 | 50 | [cell bindViewModel:_albums[indexPath.row]]; 51 | 52 | return cell; 53 | } 54 | 55 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 56 | return 50.0; 57 | } 58 | 59 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 60 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 61 | 62 | RWImageSelectorViewModel *imageSelectorViewModel = [[RWImageSelectorViewModel alloc] init]; 63 | RWAlbumViewModel *albumViewModel = _albums[indexPath.row]; 64 | imageSelectorViewModel.albumViewModel = albumViewModel; 65 | imageSelectorViewModel.title = albumViewModel.title; 66 | RWImageSelectorViewController *vc = [[RWImageSelectorViewController alloc] initWithViewModel:imageSelectorViewModel]; 67 | [self.parentViewController.navigationController pushViewController:vc animated:YES]; 68 | } 69 | 70 | #pragma mark - reload 71 | - (void)reloadData { 72 | [_tv reloadData]; 73 | } 74 | 75 | - (void)reloadWithData:(NSArray *)data { 76 | [self.albums removeAllObjects]; 77 | [self.albums setArray:data]; 78 | [_tv reloadData]; 79 | } 80 | 81 | #pragma mark - Lazy load 82 | 83 | - (UITableView *)tv { 84 | if (!_tv) { 85 | _tv = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain]; 86 | _tv.delegate = self; 87 | _tv.dataSource = self; 88 | 89 | [_tv registerNib:[UINib nibWithNibName:NSStringFromClass([RWAlbumListViewCell class]) bundle:nil] forCellReuseIdentifier:NSStringFromClass([RWAlbumListViewCell class])]; 90 | } 91 | return _tv; 92 | } 93 | 94 | - (NSMutableArray *)albums { 95 | if (!_albums) { 96 | _albums = [NSMutableArray array]; 97 | } 98 | return _albums; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWPhotoCollectView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWPhotoCollectView.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | @class RWAlbumViewModel; 11 | @interface RWPhotoCollectView : UIView 12 | 13 | - (void)reloadWithData:(RWAlbumViewModel *)viewModel; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWPhotoCollectView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWPhotoCollectView.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWPhotoCollectView.h" 10 | #import "RWPhotoViewCell.h" 11 | #import "RWAlbumViewModel.h" 12 | #import "RWimageViewModel.h" 13 | #import 14 | 15 | static CGFloat const margin = 3; 16 | static NSInteger const cols = 3; 17 | #define cellWH ((kWidth - (cols - 1) * margin) / cols) 18 | 19 | @interface RWPhotoCollectView() 20 | 21 | @property (strong, nonatomic)UICollectionView *cv; 22 | 23 | @property (strong, nonatomic)RWAlbumViewModel *albumViewModel; 24 | 25 | @property (nonatomic, assign) CGRect previousPreheatRect; 26 | 27 | @property (nonatomic, strong) PHCachingImageManager *imageManager; 28 | 29 | @end 30 | 31 | @implementation RWPhotoCollectView 32 | 33 | - (instancetype)initWithFrame:(CGRect)frame { 34 | self = [super initWithFrame:frame]; 35 | if (self) { 36 | [self setupView]; 37 | } 38 | return self; 39 | } 40 | 41 | - (PHCachingImageManager *)imageManager 42 | { 43 | if (_imageManager == nil) { 44 | _imageManager = [PHCachingImageManager new]; 45 | } 46 | return _imageManager; 47 | } 48 | 49 | - (void)setupView { 50 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 51 | layout.itemSize = CGSizeMake(cellWH, cellWH); 52 | layout.minimumInteritemSpacing = margin; 53 | layout.minimumLineSpacing = margin; 54 | 55 | _cv = [[UICollectionView alloc] initWithFrame:self.frame collectionViewLayout:layout]; 56 | _cv.delegate = self; 57 | _cv.dataSource = self; 58 | _cv.backgroundColor = [UIColor whiteColor]; 59 | 60 | [self addSubview:_cv]; 61 | 62 | [_cv registerNib:[UINib nibWithNibName:NSStringFromClass([RWPhotoViewCell class]) bundle:nil] forCellWithReuseIdentifier:NSStringFromClass([RWPhotoViewCell class])]; 63 | } 64 | 65 | - (nonnull __kindof UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath { 66 | RWPhotoViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([RWPhotoViewCell class]) forIndexPath:indexPath]; 67 | [cell bindViewModel:_albumViewModel.allAssets[indexPath.row]]; 68 | 69 | __weak typeof(self) weakSelf = self; 70 | cell.selectAction = ^(BOOL selected) { 71 | if (selected) { 72 | [weakSelf.albumViewModel selectOne:indexPath.row]; 73 | } else { 74 | [weakSelf.albumViewModel removeOne:indexPath.row]; 75 | } 76 | }; 77 | return cell; 78 | } 79 | 80 | - (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 81 | return _albumViewModel.count; 82 | } 83 | 84 | - (void)reloadData { 85 | [_cv reloadData]; 86 | } 87 | 88 | - (void)reloadWithData:(RWAlbumViewModel *)viewModel { 89 | _albumViewModel = viewModel; 90 | [_cv reloadData]; 91 | } 92 | 93 | #pragma mark - UIScrollViewDelegate 94 | 95 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 96 | { 97 | if (self.albumViewModel.allAssets.count>1) { 98 | [self updateCachedAssets]; 99 | } 100 | } 101 | 102 | - (void)updateCachedAssets{ 103 | 104 | // The preheat window is twice the height of the visible rect 105 | CGRect preheatRect = self.cv.bounds; 106 | preheatRect = CGRectInset(preheatRect, 0.0, -0.5 * CGRectGetHeight(preheatRect)); 107 | 108 | // If scrolled by a "reasonable" amount... 109 | CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect)); 110 | 111 | if (delta > CGRectGetHeight(self.cv.bounds) / 3.0) { 112 | // Compute the assets to start caching and to stop caching 113 | NSMutableArray *addedIndexPaths = [NSMutableArray array]; 114 | NSMutableArray *removedIndexPaths = [NSMutableArray array]; 115 | 116 | [self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect addedHandler:^(CGRect addedRect) { 117 | NSArray *indexPaths = [self qb_indexPathsForElementsInRect:addedRect]; 118 | [addedIndexPaths addObjectsFromArray:indexPaths]; 119 | } removedHandler:^(CGRect removedRect) { 120 | NSArray *indexPaths = [self qb_indexPathsForElementsInRect:removedRect]; 121 | [removedIndexPaths addObjectsFromArray:indexPaths]; 122 | }]; 123 | 124 | NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths]; 125 | NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths]; 126 | 127 | CGSize itemSize = CGSizeMake(cellWH, cellWH); 128 | 129 | [self.imageManager startCachingImagesForAssets:assetsToStartCaching 130 | targetSize:itemSize 131 | contentMode:PHImageContentModeAspectFill 132 | options:nil]; 133 | [self.imageManager stopCachingImagesForAssets:assetsToStopCaching 134 | targetSize:itemSize 135 | contentMode:PHImageContentModeAspectFill 136 | options:nil]; 137 | 138 | self.previousPreheatRect = preheatRect; 139 | } 140 | 141 | 142 | } 143 | 144 | - (NSArray *)qb_indexPathsForElementsInRect:(CGRect)rect 145 | { 146 | NSArray *allLayoutAttributes = [self.cv.collectionViewLayout layoutAttributesForElementsInRect:rect]; 147 | if (allLayoutAttributes.count == 0) { return nil; } 148 | 149 | NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count]; 150 | for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) { 151 | NSIndexPath *indexPath = layoutAttributes.indexPath; 152 | [indexPaths addObject:indexPath]; 153 | } 154 | return indexPaths; 155 | } 156 | 157 | - (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect addedHandler:(void (^)(CGRect addedRect))addedHandler removedHandler:(void (^)(CGRect removedRect))removedHandler 158 | { 159 | if (CGRectIntersectsRect(newRect, oldRect)) { 160 | CGFloat oldMaxY = CGRectGetMaxY(oldRect); 161 | CGFloat oldMinY = CGRectGetMinY(oldRect); 162 | CGFloat newMaxY = CGRectGetMaxY(newRect); 163 | CGFloat newMinY = CGRectGetMinY(newRect); 164 | 165 | if (newMaxY > oldMaxY) { 166 | CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY)); 167 | addedHandler(rectToAdd); 168 | } 169 | if (oldMinY > newMinY) { 170 | CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY)); 171 | addedHandler(rectToAdd); 172 | } 173 | if (newMaxY < oldMaxY) { 174 | CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY)); 175 | removedHandler(rectToRemove); 176 | } 177 | if (oldMinY < newMinY) { 178 | CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY)); 179 | removedHandler(rectToRemove); 180 | } 181 | } else { 182 | addedHandler(newRect); 183 | removedHandler(oldRect); 184 | } 185 | } 186 | 187 | - (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths 188 | { 189 | if (indexPaths.count == 0) { return nil; } 190 | NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count]; 191 | for (NSIndexPath *indexPath in indexPaths) { 192 | if (indexPath.item < self.albumViewModel.count && indexPath.item != 0) { 193 | RWimageViewModel *imageViewModel = self.albumViewModel.allAssets[self.albumViewModel.count-indexPath.item]; 194 | PHAsset *asset = imageViewModel.asset; 195 | [assets addObject:asset]; 196 | } 197 | } 198 | return assets; 199 | } 200 | 201 | @end 202 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWPhotoViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWPhotoViewCell.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void(^RWPhotoViewCellSelectAction)(BOOL selected); 12 | 13 | @class RWimageViewModel; 14 | @interface RWPhotoViewCell : UICollectionViewCell 15 | 16 | @property (copy, nonatomic)RWPhotoViewCellSelectAction selectAction; 17 | 18 | - (void)bindViewModel:(RWimageViewModel *)viewModel; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWPhotoViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWPhotoViewCell.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWPhotoViewCell.h" 10 | #import "RWimageViewModel.h" 11 | 12 | @interface RWPhotoViewCell() 13 | 14 | @property (strong, nonatomic)CALayer *imageLayer; 15 | 16 | @property (weak, nonatomic) IBOutlet UIView *bgView; 17 | 18 | @property (weak, nonatomic) IBOutlet UIButton *selectBtn; 19 | 20 | @property (copy, nonatomic)RWimageViewModel *viewModel; 21 | 22 | //@property (strong, nonatomic)UIImageView *testView; 23 | 24 | @end 25 | 26 | @implementation RWPhotoViewCell 27 | 28 | - (void)awakeFromNib { 29 | [super awakeFromNib]; 30 | // Initialization code 31 | 32 | _imageLayer = ({ 33 | CALayer *layer = [CALayer layer]; 34 | layer.backgroundColor = [UIColor grayColor].CGColor; 35 | layer.contentsGravity = kCAGravityResizeAspectFill; 36 | layer; 37 | }); 38 | [self.bgView.layer addSublayer:_imageLayer]; 39 | 40 | } 41 | 42 | - (void)bindViewModel:(RWimageViewModel *)viewModel { 43 | _viewModel = viewModel; 44 | _selectBtn.selected = _viewModel.selected; 45 | __weak typeof(self) weakSelf = self; 46 | [_viewModel loadImageDataWithPhotoWidth:150 success:^(id responseObject) { 47 | UIImage *image = (UIImage *)responseObject; 48 | [weakSelf setupImageViewByLayer:image]; 49 | } failure:^(NSError *error) { 50 | 51 | }]; 52 | } 53 | 54 | - (void)setupImageViewByLayer:(UIImage *)image { 55 | _imageLayer.frame = self.bounds; 56 | _imageLayer.contents = (id)image.CGImage; 57 | } 58 | 59 | - (IBAction)selectAction:(id)sender { 60 | 61 | UIButton *button =(UIButton *)sender; 62 | button.selected = !button.selected; 63 | _viewModel.selected = button.selected; 64 | !_selectAction?:_selectAction(_viewModel.selected); 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/View/RWPhotoViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/ViewModel/RWAlbumListViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumListViewModel.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RWBaseViewModel.h" 11 | 12 | typedef NS_ENUM(NSUInteger, RWAlbumListContentType) { 13 | RWAlbumListContentTypePhoto, 14 | RWAlbumListContentTypeVideo, 15 | }; 16 | 17 | @class RWAlbumViewModel; 18 | @interface RWAlbumListViewModel : RWBaseViewModel 19 | 20 | @property (strong, nonatomic, readonly)NSArray *albums; 21 | 22 | - (void)loadAlbumDataContentType:(RWAlbumListContentType)contentType success:(void (^)(id))success failure:(void (^)(NSError *))failure; 23 | 24 | - (void)submitAllTransferDatas; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/ViewModel/RWAlbumListViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumListViewModel.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/28. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWAlbumListViewModel.h" 10 | #import "RWAlbumViewModel.h" 11 | #import "RWTransferCenter.h" 12 | 13 | #import "RWImageLoad.h" 14 | 15 | @interface RWAlbumListViewModel() 16 | 17 | @property (strong, nonatomic, readwrite)NSArray *albums; 18 | 19 | @end 20 | 21 | @implementation RWAlbumListViewModel 22 | 23 | - (void)loadAlbumDataContentType:(RWAlbumListContentType)contentType success:(void (^)(id))success failure:(void (^)(NSError *))failure { 24 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 25 | BOOL contentImage = NO; 26 | BOOL contentVideo = NO; 27 | switch (contentType) { 28 | case RWAlbumListContentTypePhoto: 29 | { 30 | contentImage = YES; 31 | contentVideo = NO; 32 | break; 33 | } 34 | case RWAlbumListContentTypeVideo: 35 | { 36 | contentImage = NO; 37 | contentVideo = YES; 38 | break; 39 | } 40 | 41 | default: 42 | break; 43 | } 44 | 45 | [[RWImageLoad shareLoad] getAlbumContentImage:contentImage contentVideo:contentVideo completion:^(NSMutableArray *albums) { 46 | NSMutableArray *array = [NSMutableArray array]; 47 | for (RWAlbumModel *model in albums) { 48 | RWAlbumViewModel *viewModel = [[RWAlbumViewModel alloc] initWithModel:model]; 49 | [array addObject:viewModel]; 50 | } 51 | self.albums = (NSArray *)array; 52 | !success?:success(nil); 53 | }]; 54 | }); 55 | } 56 | 57 | - (void)submitAllTransferDatas { 58 | NSMutableArray *array = [NSMutableArray array]; 59 | for (RWAlbumViewModel *albums in _albums) { 60 | [array addObjectsFromArray:[albums returnSelectedModels]]; 61 | } 62 | 63 | [[RWTransferCenter center] setupReadyTaskDatas:array]; 64 | } 65 | 66 | -(NSArray *)albums { 67 | if (!_albums) { 68 | _albums = [NSMutableArray array]; 69 | } 70 | return _albums; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/ViewModel/RWAlbumViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumViewModel.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/27. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | @class PHFetchResult,RWAlbumModel; 11 | @interface RWAlbumViewModel : NSObject 12 | 13 | @property (copy, nonatomic, readonly)NSString *title; 14 | 15 | @property (copy, nonatomic, readonly)PHFetchResult *result; 16 | 17 | @property (assign, nonatomic, readonly)NSInteger count; 18 | 19 | @property (strong, nonatomic, readonly)NSArray *allAssets; 20 | 21 | @property (strong, nonatomic, readwrite)NSMutableArray *selectedAssets; 22 | 23 | @property (assign, nonatomic, readonly)NSInteger selectedCount; 24 | 25 | @property (assign, nonatomic, readonly)BOOL selected; 26 | 27 | @property (assign, nonatomic, readonly)NSString *fileType; 28 | 29 | - (instancetype)initWithModel:(RWAlbumModel *)model; 30 | 31 | - (void)selectOne:(NSInteger)index; 32 | 33 | - (void)selectedAll; 34 | 35 | - (void)removeOne:(NSInteger)index; 36 | 37 | - (void)removeAll; 38 | 39 | - (NSArray *)returnSelectedModels; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/ViewModel/RWAlbumViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWAlbumViewModel.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/27. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWAlbumViewModel.h" 10 | #import "RWAlbumModel.h" 11 | #import "RWPhotoModel.h" 12 | #import "RWimageViewModel.h" 13 | 14 | @interface RWAlbumViewModel() 15 | 16 | @property (strong, nonatomic)RWAlbumModel *albumModel; 17 | 18 | @property (copy, nonatomic, readwrite)NSString *title; 19 | 20 | @property (copy, nonatomic, readwrite)PHFetchResult *result; 21 | 22 | @property (assign, nonatomic, readwrite)NSInteger count; 23 | 24 | @property (strong, nonatomic, readwrite)NSArray *allAssets; 25 | 26 | @property (assign, nonatomic, readwrite)NSInteger selectedCount; 27 | 28 | @property (assign, nonatomic, readwrite)NSString *fileType; 29 | 30 | @end 31 | 32 | @implementation RWAlbumViewModel 33 | 34 | - (instancetype)initWithModel:(RWAlbumModel *)model { 35 | self = [super init]; 36 | if (self) { 37 | _albumModel = model; 38 | _title = model.title; 39 | _result = model.result; 40 | _count = model.count; 41 | _fileType = model.fileType; 42 | _allAssets = [self readAllAssets]; 43 | _selectedAssets = [NSMutableArray array]; 44 | } 45 | return self; 46 | } 47 | 48 | - (NSArray *)readAllAssets { 49 | NSMutableArray *photos = [NSMutableArray arrayWithCapacity:_result.count]; 50 | NSRange range = NSMakeRange(0, _result.count); 51 | NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:range]; 52 | NSArray *array = [[_result objectsAtIndexes:indexSet] mutableCopy]; 53 | 54 | for (PHAsset *asset in array) { 55 | RWPhotoModel *photoModel = [[RWPhotoModel alloc] init]; 56 | photoModel.name = [self getFileNameWith:asset]; 57 | photoModel.asset = asset; 58 | photoModel.fileType = _fileType; 59 | photoModel.pathExtension = [photoModel.name pathExtension]; 60 | RWimageViewModel *imageViewModel = [[RWimageViewModel alloc] initWithModel:photoModel]; 61 | [photos addObject:imageViewModel]; 62 | } 63 | return photos; 64 | } 65 | 66 | - (NSString *)getFileNameWith:(PHAsset *)asset { 67 | NSString *name = [NSString stringWithFormat:@"%ld", (long)([[NSDate date] timeIntervalSince1970] * 100000)]; 68 | name = [name stringByReplacingOccurrencesOfString:@"." withString:@"_"]; 69 | NSString *pathExtension = [self getPathExtensionWith:asset]; 70 | name = [NSString stringWithFormat:@"%@.%@", name, pathExtension]; 71 | return name; 72 | } 73 | 74 | - (NSString *)getPathExtensionWith:(PHAsset *)asset { 75 | NSString *filename = [asset valueForKey:@"filename"]; 76 | NSString *pathExtension = [filename pathExtension]; 77 | return pathExtension; 78 | } 79 | 80 | - (void)selectOne:(NSInteger)index { 81 | RWimageViewModel *imageViewModel = _allAssets[index]; 82 | if (![_selectedAssets containsObject:imageViewModel]) { 83 | [_selectedAssets addObject:imageViewModel]; 84 | } 85 | } 86 | 87 | - (void)selectedAll { 88 | _selectedAssets = [_allAssets mutableCopy]; 89 | for (RWimageViewModel *viewModel in _selectedAssets) { 90 | viewModel.selected = YES; 91 | } 92 | } 93 | 94 | - (void)removeOne:(NSInteger)index { 95 | RWimageViewModel *imageViewModel = _allAssets[index]; 96 | if ([_selectedAssets containsObject:imageViewModel]) { 97 | [_selectedAssets removeObject:imageViewModel]; 98 | } 99 | } 100 | 101 | - (void)removeAll { 102 | for (RWimageViewModel *viewModel in _selectedAssets) { 103 | viewModel.selected = NO; 104 | } 105 | [_selectedAssets removeAllObjects]; 106 | } 107 | 108 | - (BOOL)selected { 109 | if (self.count == self.selectedCount) { 110 | return YES; 111 | } else { 112 | return NO; 113 | } 114 | } 115 | 116 | - (NSInteger)selectedCount { 117 | return _selectedAssets.count; 118 | } 119 | 120 | -(NSArray *)returnSelectedModels { 121 | NSMutableArray *array = [NSMutableArray array]; 122 | for (RWimageViewModel *imageModel in _selectedAssets) { 123 | [array addObject:imageModel.model]; 124 | } 125 | return array; 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/ViewModel/RWImageSelectorViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWImageSelectorViewModel.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWBaseViewModel.h" 10 | 11 | @class RWAlbumViewModel; 12 | @interface RWImageSelectorViewModel : RWBaseViewModel 13 | 14 | @property (strong, nonatomic)RWAlbumViewModel *albumViewModel; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/ViewModel/RWImageSelectorViewModel.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // RWImageSelectorViewModel.m 4 | // RWFileTransferDemo 5 | // 6 | // Created by RyanWong on 2018/3/1. 7 | // Copyright © 2018年 RyanWong. All rights reserved. 8 | // 9 | 10 | #import "RWImageSelectorViewModel.h" 11 | #import "RWAlbumViewModel.h" 12 | #import "RWImageLoad.h" 13 | 14 | @implementation RWImageSelectorViewModel 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/ViewModel/RWimageViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWimageViewModel.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "RWBaseViewModel.h" 12 | 13 | @class RWPhotoModel; 14 | @interface RWimageViewModel : RWBaseViewModel 15 | 16 | @property (copy, nonatomic)RWPhotoModel *model; 17 | 18 | @property (copy, nonatomic, readonly)NSString *name; 19 | 20 | @property (copy, nonatomic, readonly)PHAsset *asset; 21 | 22 | @property (assign, nonatomic, readonly)long long size; 23 | 24 | @property (copy, nonatomic, readonly)NSString *fileType; 25 | 26 | @property (copy, nonatomic, readonly)NSString *pathExtension; 27 | 28 | @property (assign, nonatomic)BOOL selected; 29 | 30 | - (instancetype)initWithModel:(RWPhotoModel *)model; 31 | 32 | - (void)loadImageDataWithPhotoWidth:(CGFloat)photoWidth success:(void(^)(id))success failure:(void (^)(NSError *))failure; 33 | 34 | - (void)loadVideoDataSuccess:(void(^)(long long size, UIImage *image))success failure:(void (^)(NSError *))failure; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/ImageSelector/ViewModel/RWimageViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWimageViewModel.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/1. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWimageViewModel.h" 10 | #import "RWPhotoModel.h" 11 | #import "RWImageLoad.h" 12 | #import 13 | 14 | @interface RWimageViewModel() 15 | 16 | @property (copy, nonatomic, readwrite)NSString *name; 17 | 18 | @property (copy, nonatomic, readwrite)PHAsset *asset; 19 | 20 | @property (assign, nonatomic, readwrite)long long size; 21 | 22 | @property (copy, nonatomic, readwrite)NSString *fileType; 23 | 24 | @property (copy, nonatomic, readwrite)NSString *pathExtension; 25 | 26 | @end 27 | 28 | @implementation RWimageViewModel 29 | 30 | - (instancetype)initWithModel:(RWPhotoModel *)model { 31 | self = [super init]; 32 | if (self) { 33 | _model = model; 34 | _name = model.name; 35 | _asset = model.asset; 36 | _size = model.size; 37 | _fileType = model.fileType; 38 | _pathExtension = model.pathExtension; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)loadImageDataWithPhotoWidth:(CGFloat)photoWidth success:(void(^)(id))success failure:(void (^)(NSError *))failure { 44 | 45 | [[RWImageLoad shareLoad] getPhotoWithAsset:_asset photoWidth:photoWidth completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { 46 | !success?:success(photo); 47 | } progressHandler:nil networkAccessAllowed:NO]; 48 | } 49 | 50 | - (void)loadVideoDataSuccess:(void(^)(long long size, UIImage *image))success failure:(void (^)(NSError *))failure { 51 | 52 | [[RWImageLoad shareLoad] getVideoInfoWithAsset:_asset completion:^(long long size, UIImage *image) { 53 | !success?:success(size, image); 54 | }]; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/VideoSelector/Controller/RWVideoListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWVideoListViewController.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/9. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWBaseViewController.h" 10 | 11 | @interface RWVideoListViewController : RWBaseViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/VideoSelector/Controller/RWVideoListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWVideoListViewController.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/9. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWVideoListViewController.h" 10 | #import "RWAlbumListViewModel.h" 11 | #import "RWVideoTableView.h" 12 | 13 | @interface RWVideoListViewController () 14 | 15 | @property (strong, nonatomic)RWVideoTableView *tv; 16 | 17 | @property (strong, nonatomic)RWAlbumListViewModel *viewModel; 18 | 19 | @property (strong, nonatomic)UIButton *sendBtn; 20 | 21 | @end 22 | 23 | @implementation RWVideoListViewController 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | 28 | self.viewModel = (RWAlbumListViewModel *)self.baseViewModel; 29 | 30 | self.title = self.viewModel.title; 31 | 32 | [self.view addSubview:self.tv]; 33 | 34 | [self.view addSubview:self.sendBtn]; 35 | 36 | [self.viewModel loadAlbumDataContentType:RWAlbumListContentTypeVideo success:^(id responseObject) { 37 | NSLog(@"%@",self.viewModel.albums); 38 | [self.tv reloadWithData:self.viewModel.albums]; 39 | } failure:^(NSError *error) { 40 | 41 | }]; 42 | 43 | } 44 | 45 | - (void)sendAction { 46 | [self.viewModel submitAllTransferDatas]; 47 | [self.navigationController popViewControllerAnimated:YES]; 48 | } 49 | 50 | - (void)didReceiveMemoryWarning { 51 | [super didReceiveMemoryWarning]; 52 | // Dispose of any resources that can be recreated. 53 | } 54 | 55 | -(RWVideoTableView *)tv { 56 | if (!_tv) { 57 | _tv = [[RWVideoTableView alloc] initWithFrame:self.view.frame]; 58 | } 59 | return _tv; 60 | } 61 | 62 | -(UIButton *)sendBtn { 63 | if (!_sendBtn) { 64 | _sendBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 50, self.view.frame.size.width, 50)]; 65 | [_sendBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 66 | [_sendBtn setTitle:@"发送" forState:UIControlStateNormal]; 67 | [_sendBtn addTarget:self action:@selector(sendAction) forControlEvents:UIControlEventTouchUpInside]; 68 | } 69 | return _sendBtn; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/VideoSelector/View/RWVideoListViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWVideoListViewCell.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/9. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void(^RWVideoListViewCellSelectAction)(BOOL selected); 12 | 13 | @class RWimageViewModel; 14 | @interface RWVideoListViewCell : UITableViewCell 15 | 16 | @property (copy, nonatomic) RWVideoListViewCellSelectAction selectAction; 17 | 18 | - (void)bindViewModel:(RWimageViewModel *)viewModel; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/VideoSelector/View/RWVideoListViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWVideoListViewCell.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/9. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWVideoListViewCell.h" 10 | #import "RWimageViewModel.h" 11 | 12 | @interface RWVideoListViewCell() 13 | 14 | @property (weak, nonatomic) IBOutlet UIImageView *coverV; 15 | @property (weak, nonatomic) IBOutlet UILabel *titleLab; 16 | @property (weak, nonatomic) IBOutlet UILabel *sizeLab; 17 | @property (weak, nonatomic) IBOutlet UIButton *selectBtn; 18 | 19 | @property (strong, nonatomic) RWimageViewModel *viewModel; 20 | 21 | @end 22 | 23 | @implementation RWVideoListViewCell 24 | 25 | - (void)awakeFromNib { 26 | [super awakeFromNib]; 27 | // Initialization code 28 | } 29 | 30 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 31 | [super setSelected:selected animated:animated]; 32 | 33 | // Configure the view for the selected state 34 | } 35 | 36 | - (void)bindViewModel:(RWimageViewModel *)viewModel { 37 | _viewModel = viewModel; 38 | 39 | _titleLab.text = _viewModel.name; 40 | _selectBtn.selected = _viewModel.selected; 41 | 42 | __weak typeof(self) weakSelf = self; 43 | [_viewModel loadVideoDataSuccess:^(long long size, UIImage *image) { 44 | dispatch_async(dispatch_get_main_queue(), ^{ 45 | weakSelf.coverV.image = image; 46 | weakSelf.sizeLab.text = [NSString stringWithFormat:@"%.2fMB",(CGFloat)(size / 1024.0 / 1024.0)]; 47 | }); 48 | } failure:^(NSError *error) { 49 | 50 | }]; 51 | } 52 | 53 | - (IBAction)selectAction:(id)sender { 54 | UIButton *button = (UIButton *)sender; 55 | button.selected = !button.selected; 56 | _viewModel.selected = button.selected; 57 | !_selectAction?:_selectAction(_viewModel.selected); 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/VideoSelector/View/RWVideoListViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 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 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/VideoSelector/View/RWVideoTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWVideoTableView.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/9. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RWAlbumViewModel; 12 | @interface RWVideoTableView : UIView 13 | 14 | - (void)reloadWithData:(NSArray *)data; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/File/VideoSelector/View/RWVideoTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWVideoTableView.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/9. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWVideoTableView.h" 10 | #import "RWVideoListViewCell.h" 11 | #import "RWAlbumViewModel.h" 12 | 13 | @interface RWVideoTableView() 14 | 15 | @property (strong, nonatomic) UITableView *tv; 16 | 17 | @property (strong, nonatomic)NSMutableArray *albums; 18 | 19 | @property (strong, nonatomic)RWAlbumViewModel *albumViewModel; 20 | 21 | @end 22 | 23 | @implementation RWVideoTableView 24 | 25 | - (instancetype) initWithFrame:(CGRect)frame { 26 | self = [super initWithFrame:frame]; 27 | if (self) { 28 | [self setupView]; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)setupView { 34 | [self addSubview:self.tv]; 35 | } 36 | 37 | #pragma mark - TableView delegate datasource 38 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 39 | return _albumViewModel.allAssets.count; 40 | } 41 | 42 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 43 | RWVideoListViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([RWVideoListViewCell class]) forIndexPath:indexPath]; 44 | 45 | [cell bindViewModel:_albumViewModel.allAssets[indexPath.row]]; 46 | 47 | __weak typeof(self) weakSelf = self; 48 | cell.selectAction = ^(BOOL selected) { 49 | if (selected) { 50 | [weakSelf.albumViewModel selectOne:indexPath.row]; 51 | } else { 52 | [weakSelf.albumViewModel removeOne:indexPath.row]; 53 | } 54 | }; 55 | return cell; 56 | } 57 | 58 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 59 | return 70.0; 60 | } 61 | 62 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 63 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 64 | } 65 | 66 | #pragma mark - reload 67 | - (void)reloadData { 68 | [_tv reloadData]; 69 | } 70 | 71 | - (void)reloadWithData:(NSArray *)data { 72 | // [self.albums removeAllObjects]; 73 | // [self.albums setArray:data]; 74 | _albumViewModel = data[0]; 75 | [_tv reloadData]; 76 | } 77 | 78 | #pragma mark - Lazy load 79 | 80 | - (UITableView *)tv { 81 | if (!_tv) { 82 | _tv = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain]; 83 | _tv.delegate = self; 84 | _tv.dataSource = self; 85 | 86 | [_tv registerNib:[UINib nibWithNibName:NSStringFromClass([RWVideoListViewCell class]) bundle:nil] forCellReuseIdentifier:NSStringFromClass([RWVideoListViewCell class])]; 87 | } 88 | return _tv; 89 | } 90 | 91 | - (NSMutableArray *)albums { 92 | if (!_albums) { 93 | _albums = [NSMutableArray array]; 94 | } 95 | return _albums; 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Search/Controller/SearchViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SearchViewController.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SearchViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Search/Controller/SearchViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SearchViewController.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "SearchViewController.h" 10 | #import "RWBrowser.h" 11 | #import "RWSession.h" 12 | #import "RWUserCenter.h" 13 | #import "TransferListViewController.h" 14 | 15 | @interface SearchViewController () 16 | 17 | @property (strong, nonatomic)NSMutableArray *peerArray; 18 | 19 | @end 20 | 21 | @implementation SearchViewController 22 | 23 | - (void)viewDidDisappear:(BOOL)animated{ 24 | [super viewDidDisappear:animated]; 25 | 26 | [[RWBrowser shareInstance] stopSearch]; 27 | [RWSession kickPeer:[RWUserCenter center].myPeerID]; 28 | 29 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 30 | } 31 | 32 | - (void)viewWillAppear:(BOOL)animated { 33 | [super viewWillAppear:animated]; 34 | 35 | [[RWUserCenter center].session.session disconnect]; 36 | 37 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connect) name:kRWSessionStateConnectedNotification object:nil]; 38 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notConnect) name:kRWSessionStateNotConnectedNotification object:nil]; 39 | } 40 | 41 | - (void)viewDidLoad { 42 | [super viewDidLoad]; 43 | 44 | self.title = @"搜索设备"; 45 | 46 | NSString *deviceName = [UIDevice currentDevice].name; 47 | [[RWBrowser shareInstance] setConfigurationWithName:deviceName Identifier:@"rw"]; 48 | 49 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"PeerIdCell"]; 50 | 51 | [[RWBrowser shareInstance] startSearchNearbyService]; 52 | [RWBrowser shareInstance].nearbyPeerBlock = ^(NSArray *peerArray) { 53 | [self.peerArray setArray:peerArray]; 54 | [self.tableView reloadData]; 55 | }; 56 | } 57 | 58 | - (void)connect { 59 | dispatch_async(dispatch_get_main_queue(), ^{ 60 | RWTransferListViewModel *vm = [[RWTransferListViewModel alloc] init]; 61 | vm.title = @"传输圈"; 62 | TransferListViewController *vc = [[TransferListViewController alloc] initWithViewModel:vm]; 63 | [self.navigationController pushViewController:vc animated:YES]; 64 | }); 65 | } 66 | 67 | - (void)notConnect { 68 | dispatch_async(dispatch_get_main_queue(), ^{ 69 | [self.navigationController popViewControllerAnimated:YES]; 70 | }); 71 | } 72 | 73 | - (void)didReceiveMemoryWarning { 74 | [super didReceiveMemoryWarning]; 75 | // Dispose of any resources that can be recreated. 76 | } 77 | 78 | #pragma mark - Table view data source 79 | 80 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 81 | return 1; 82 | } 83 | 84 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 85 | return self.peerArray.count; 86 | } 87 | 88 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 89 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PeerIdCell" forIndexPath:indexPath]; 90 | MCPeerID *peerId = self.peerArray[indexPath.row]; 91 | cell.textLabel.text = peerId.displayName; 92 | return cell; 93 | } 94 | 95 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 96 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 97 | 98 | MCPeerID *peerId = self.peerArray[indexPath.row]; 99 | [[RWBrowser shareInstance] invitePeer:peerId]; 100 | } 101 | 102 | - (NSMutableArray *)peerArray { 103 | if (!_peerArray) { 104 | _peerArray = [NSMutableArray array]; 105 | } 106 | return _peerArray; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/Controller/TransferListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TransferViewController.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RWBaseViewController.h" 11 | #import "RWTransferListViewModel.h" 12 | @interface TransferListViewController : RWBaseViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/Controller/TransferListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TransferViewController.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "TransferListViewController.h" 10 | #import "RWAlbumListViewController.h" 11 | #import "RWVideoListViewController.h" 12 | #import "RWTransferListView.h" 13 | #import "RWTransferViewModel.h" 14 | #import "RWTransferCenter.h" 15 | 16 | #import "RWUserCenter.h" 17 | #import "RWSession.h" 18 | #import "RWOutputStream.h" 19 | #import "RWInputStream.h" 20 | 21 | #import "RWFileManager.h" 22 | #import "RWFileHandle.h" 23 | 24 | @interface TransferListViewController () 25 | 26 | @property (strong, nonatomic)RWTransferListViewModel *viewModel; 27 | 28 | @property (strong, nonatomic)RWTransferListView *transferListView; 29 | 30 | @property (strong, nonatomic)RWSession *session; 31 | @property (strong, nonatomic)RWOutputStream *outputStream; 32 | @property (strong, nonatomic)RWInputStream *inputStream; 33 | 34 | @end 35 | 36 | @implementation TransferListViewController 37 | 38 | - (void)viewWillAppear:(BOOL)animated { 39 | [super viewWillAppear:animated]; 40 | 41 | [self sendTaskInfo]; 42 | } 43 | 44 | - (void)viewDidLoad { 45 | [super viewDidLoad]; 46 | 47 | _viewModel = (RWTransferListViewModel *)self.baseViewModel; 48 | [_viewModel setTarget:self]; 49 | 50 | self.title = _viewModel.title; 51 | 52 | [self.view addSubview:self.transferListView]; 53 | 54 | self.session.delegate = self; 55 | 56 | UIBarButtonItem *chooseBtn = [[UIBarButtonItem alloc] initWithTitle:@"选择文件" style:UIBarButtonItemStylePlain target:self action:@selector(chooseAction)]; 57 | self.navigationItem.rightBarButtonItem = chooseBtn; 58 | 59 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notConnect) name:kRWSessionStateNotConnectedNotification object:nil]; 60 | 61 | [self registerSenderTaskListener]; 62 | } 63 | 64 | - (void)notConnect { 65 | dispatch_async(dispatch_get_main_queue(), ^{ 66 | [self.navigationController popViewControllerAnimated:YES]; 67 | }); 68 | } 69 | 70 | - (void)chooseAction { 71 | UIAlertController *alertCtrl = [UIAlertController alertControllerWithTitle:@"请选择发送的文件目录" message:nil preferredStyle:UIAlertControllerStyleActionSheet]; 72 | 73 | [alertCtrl addAction:[UIAlertAction actionWithTitle:@"照片" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 74 | dispatch_async(dispatch_get_main_queue(), ^{ 75 | RWAlbumListViewModel *viewModel = [[RWAlbumListViewModel alloc] init]; 76 | viewModel.title = @"选择相册"; 77 | RWAlbumListViewController *vc = [[RWAlbumListViewController alloc] initWithViewModel:viewModel]; 78 | [self.navigationController pushViewController:vc animated:YES]; 79 | }); 80 | }]]; 81 | 82 | [alertCtrl addAction:[UIAlertAction actionWithTitle:@"视频" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 83 | dispatch_async(dispatch_get_main_queue(), ^{ 84 | RWAlbumListViewModel *viewModel = [[RWAlbumListViewModel alloc] init]; 85 | viewModel.title = @"选择视频"; 86 | RWVideoListViewController *vc = [[RWVideoListViewController alloc] initWithViewModel:viewModel]; 87 | [self.navigationController pushViewController:vc animated:YES]; 88 | }); 89 | }]]; 90 | 91 | [alertCtrl addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 92 | 93 | }]]; 94 | 95 | [self.navigationController presentViewController:alertCtrl animated:YES completion:nil]; 96 | } 97 | 98 | - (void)sendTaskInfo { 99 | [_viewModel sendPeerTaskInfo]; 100 | [_transferListView reloadTableView]; 101 | } 102 | 103 | - (void)didReceiveMemoryWarning { 104 | [super didReceiveMemoryWarning]; 105 | // Dispose of any resources that can be recreated. 106 | } 107 | 108 | #pragma mark - RWSession Delegate 109 | 110 | -(void)session:(RWSession *)session didReceiveData:(NSData *)data { 111 | [_viewModel handleReceiveData:data]; 112 | } 113 | 114 | -(void)session:(RWSession *)session didReceiveStream:(NSInputStream *)stream WithName:(NSString *)streamName { 115 | [_viewModel createReceiveStreamWithStream:stream streamName:streamName]; 116 | [_transferListView reloadTableView]; 117 | } 118 | 119 | #pragma mark - RWOutputStream Delegate 120 | 121 | -(void)outputStream:(RWOutputStream *)outputStream progress:(long long)progress { 122 | 123 | } 124 | 125 | -(void)outputStream:(RWOutputStream *)outputStream transferEndWithStreamName:(NSString *)name { 126 | outputStream.delegate = nil; 127 | outputStream = nil; 128 | 129 | __weak typeof(self) weakSelf = self; 130 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 131 | [weakSelf.viewModel nextReadyTask]; 132 | [self sendTaskInfo]; 133 | }); 134 | } 135 | 136 | -(void)outputStream:(RWOutputStream *)outputStream transferErrorWithStreamName:(NSString *)name { 137 | outputStream.delegate = nil; 138 | outputStream = nil; 139 | 140 | // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 141 | // [self sendFile]; 142 | // }); 143 | } 144 | 145 | #pragma mark - RWInputStream Delegate 146 | - (void)inputStream:(RWInputStream *)inputStream streamName:(NSString *)streamName progress:(long long)progress { 147 | [_viewModel receiveTaskProgressWithStreamName:streamName progress:progress]; 148 | NSInteger index = [_viewModel getTaskIndexWithStreamName:streamName]; 149 | [_transferListView reloadProgressCell:index]; 150 | } 151 | 152 | - (void)inputStream:(RWInputStream *)inputStream transferEndWithStreamName:(NSString *)streamName filePath:(NSString *)filePath { 153 | __weak typeof(self) weakSelf = self; 154 | [_viewModel handleTmpFile:filePath name:streamName completion:^(NSString *targetName) { 155 | NSInteger index = [weakSelf.viewModel getTaskIndexWithStreamName:targetName]; 156 | [weakSelf.transferListView reloadSingleCell:index]; 157 | }]; 158 | [_viewModel receiveTaskFinishWithStreamName:streamName]; 159 | 160 | [inputStream stop]; 161 | inputStream.delegate = nil; 162 | inputStream = nil; 163 | 164 | NSInteger index = [_viewModel getTaskIndexWithStreamName:streamName]; 165 | [_transferListView reloadSingleCell:index]; 166 | } 167 | 168 | - (void)inputStream:(RWInputStream *)inputStream transferErrorWithStreamName:(NSString *)streamName { 169 | [_viewModel receiveTaskErrorWithStreamName:streamName]; 170 | 171 | [inputStream stop]; 172 | inputStream.delegate = nil; 173 | inputStream = nil; 174 | 175 | NSInteger index = [_viewModel getTaskIndexWithStreamName:streamName]; 176 | [_transferListView reloadSingleCell:index]; 177 | } 178 | 179 | #pragma mark - Data From Receiver 180 | - (void)registerSenderTaskListener { 181 | __weak typeof(self) weakSelf = self; 182 | [_viewModel sendTaskBegin:^(NSDictionary *dict) { 183 | NSString *streamName = dict[@"timestamp"]; 184 | NSInteger index = [weakSelf.viewModel getTaskIndexWithStreamName:streamName]; 185 | [weakSelf.transferListView reloadSingleCell:index]; 186 | }]; 187 | 188 | [_viewModel sendTaskProgress:^(NSDictionary *dict) { 189 | NSString *streamName = dict[@"timestamp"]; 190 | NSInteger index = [weakSelf.viewModel getTaskIndexWithStreamName:streamName]; 191 | [weakSelf.transferListView reloadProgressCell:index]; 192 | }]; 193 | 194 | [_viewModel sendTaskFinish:^(NSDictionary *dict) { 195 | NSString *streamName = dict[@"timestamp"]; 196 | NSInteger index = [weakSelf.viewModel getTaskIndexWithStreamName:streamName]; 197 | [weakSelf.transferListView reloadSingleCell:index]; 198 | 199 | // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 200 | // [_viewModel nextReadyTask]; 201 | // [self sendTaskInfo]; 202 | // }); 203 | }]; 204 | 205 | [_viewModel sendTaskError:^(NSDictionary *dict) { 206 | NSString *streamName = dict[@"timestamp"]; 207 | NSInteger index = [weakSelf.viewModel getTaskIndexWithStreamName:streamName]; 208 | [weakSelf.transferListView reloadSingleCell:index]; 209 | }]; 210 | } 211 | 212 | #pragma mark - Lazy load 213 | -(RWTransferListView *)transferListView { 214 | if (!_transferListView) { 215 | _transferListView = [[RWTransferListView alloc] initWithFrame:self.view.frame]; 216 | } 217 | return _transferListView; 218 | } 219 | 220 | -(RWSession *)session { 221 | return [RWUserCenter center].session; 222 | } 223 | 224 | -(void)dealloc { 225 | RWStatus(@"传输圈销毁"); 226 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 227 | [[RWUserCenter center] remove]; 228 | [[RWTransferCenter center] removeAllTaskData]; 229 | } 230 | 231 | @end 232 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/View/RWTransferListCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferListCell.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/22. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RWTransferViewModel; 12 | @interface RWTransferListCell : UITableViewCell 13 | 14 | - (void)bindViewModel:(RWTransferViewModel *)viewModel; 15 | 16 | - (void)inNormalModel:(RWTransferViewModel *)viewModel; 17 | 18 | - (void)inProgressModel:(RWTransferViewModel *)viewModel; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/View/RWTransferListCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferListCell.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/22. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWTransferListCell.h" 10 | #import "RWTransferViewModel.h" 11 | #import "RWImageLoad.h" 12 | 13 | static float const imageWidth = 50; 14 | static float const spaceWidth = 10; 15 | 16 | static float const buttonWidth = 50; 17 | 18 | @interface RWTransferListCell() 19 | 20 | @property (strong, nonatomic)CALayer *imageLayer; 21 | @property (strong, nonatomic)CATextLayer *nameLayer; 22 | @property (strong, nonatomic)CATextLayer *statusLayer; 23 | 24 | @property (strong, nonatomic)CALayer *progressLayer; 25 | @property (strong, nonatomic)CALayer *progressBgLayer; 26 | @property (strong, nonatomic)CATextLayer *sizeLayer; 27 | @property (strong, nonatomic)UIButton *cancelBtn; 28 | @property (assign, nonatomic)CGFloat fullProgressWidth; 29 | 30 | @property (strong, nonatomic)RWTransferViewModel *viewModel; 31 | 32 | @end 33 | 34 | @implementation RWTransferListCell 35 | 36 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 37 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 38 | if (self) { 39 | [self setupView]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)setupView { 45 | _imageLayer = ({ 46 | CALayer *layer = [CALayer layer]; 47 | layer.frame = CGRectMake(spaceWidth, spaceWidth, imageWidth, imageWidth); 48 | layer.backgroundColor = [UIColor grayColor].CGColor; 49 | layer.contentsGravity = kCAGravityResize; 50 | layer; 51 | }); 52 | [self.contentView.layer addSublayer:_imageLayer]; 53 | 54 | _nameLayer = ({ 55 | CGFloat width = kWidth - spaceWidth - imageWidth - spaceWidth - spaceWidth; 56 | CATextLayer *nameLayer = [CATextLayer layer]; 57 | nameLayer.frame = CGRectMake(spaceWidth + imageWidth + spaceWidth, spaceWidth, width, 20); 58 | nameLayer.contentsScale = [UIScreen mainScreen].scale; 59 | nameLayer.foregroundColor =[UIColor blackColor].CGColor; 60 | nameLayer.fontSize = 15.0f; 61 | nameLayer; 62 | }); 63 | [self.contentView.layer addSublayer:_nameLayer]; 64 | 65 | _statusLayer = ({ 66 | CGFloat width = kWidth - spaceWidth - imageWidth - spaceWidth - spaceWidth; 67 | CATextLayer *statusLayer = [CATextLayer layer]; 68 | statusLayer.frame = CGRectMake(spaceWidth + imageWidth + spaceWidth, 35, width, 20); 69 | statusLayer.contentsScale = [UIScreen mainScreen].scale; 70 | statusLayer.foregroundColor =[UIColor blackColor].CGColor; 71 | statusLayer.fontSize = 13.0f; 72 | statusLayer; 73 | }); 74 | [_statusLayer setHidden:YES]; 75 | [self.contentView.layer addSublayer:_statusLayer]; 76 | 77 | 78 | _fullProgressWidth = kWidth - spaceWidth - imageWidth - spaceWidth - spaceWidth - buttonWidth - spaceWidth; 79 | _progressBgLayer = ({ 80 | CALayer *layer = [CALayer layer]; 81 | layer.frame = CGRectMake(spaceWidth + imageWidth + spaceWidth, 35, _fullProgressWidth, 2); 82 | layer.backgroundColor = [UIColor grayColor].CGColor; 83 | layer; 84 | }); 85 | [_progressBgLayer setHidden:YES]; 86 | [self.contentView.layer addSublayer:_progressBgLayer]; 87 | 88 | _progressLayer = ({ 89 | CALayer *layer = [CALayer layer]; 90 | layer.frame = CGRectMake(spaceWidth + imageWidth + spaceWidth, 35, 0, 2); 91 | layer.backgroundColor = [UIColor blueColor].CGColor; 92 | layer; 93 | }); 94 | [_progressLayer setHidden:YES]; 95 | [self.contentView.layer addSublayer:_progressLayer]; 96 | 97 | _sizeLayer = ({ 98 | CATextLayer *statusLayer = [CATextLayer layer]; 99 | statusLayer.frame = CGRectMake(spaceWidth + imageWidth + spaceWidth, 40, _fullProgressWidth, 20); 100 | statusLayer.contentsScale = [UIScreen mainScreen].scale; 101 | statusLayer.foregroundColor =[UIColor blackColor].CGColor; 102 | statusLayer.fontSize = 13.0f; 103 | statusLayer; 104 | }); 105 | [_sizeLayer setHidden:YES]; 106 | [self.contentView.layer addSublayer:_sizeLayer]; 107 | 108 | _cancelBtn = ({ 109 | UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(spaceWidth + imageWidth + spaceWidth +_fullProgressWidth + spaceWidth, 10, buttonWidth, buttonWidth)]; 110 | [button setBackgroundColor:[UIColor redColor]]; 111 | [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 112 | [button setTitle:@"取消" forState:UIControlStateNormal]; 113 | button; 114 | }); 115 | [_cancelBtn setHidden:YES]; 116 | [self.contentView addSubview:_cancelBtn]; 117 | } 118 | 119 | - (void)bindViewModel:(RWTransferViewModel *)viewModel { 120 | _viewModel = viewModel; 121 | 122 | _nameLayer.string = viewModel.name; 123 | 124 | if (viewModel.cover) { 125 | _imageLayer.contents = (id)viewModel.cover.CGImage; 126 | } else { 127 | __weak typeof(self)weakSelf = self; 128 | if (viewModel.asset) { 129 | [viewModel loadImageDataWithPhotoWidth:150 success:^(id responseObject) { 130 | UIImage *image = (UIImage *)responseObject; 131 | weakSelf.imageLayer.contents = (id)image.CGImage; 132 | } failure:^(NSError *error) { 133 | 134 | }]; 135 | } else if (viewModel.sandboxPath) { 136 | [viewModel loadSandBoxImageWithIsCoverSize:YES completion:^(UIImage *coverImage) { 137 | weakSelf.imageLayer.contents = (id)coverImage.CGImage; 138 | }]; 139 | } 140 | } 141 | 142 | if (viewModel.status == RWTransferStatusTransfer) { 143 | [self inProgressModel:viewModel]; 144 | } else { 145 | [self inNormalModel:viewModel]; 146 | } 147 | } 148 | 149 | - (void)inNormalModel:(RWTransferViewModel *)viewModel { 150 | 151 | [self setProgress:0.0]; 152 | 153 | [_statusLayer setHidden:NO]; 154 | [_sizeLayer setHidden:YES]; 155 | [_progressLayer setHidden:YES]; 156 | [_progressBgLayer setHidden:YES]; 157 | [_cancelBtn setHidden:YES]; 158 | 159 | NSString *sizeStr = [RWCommon getFileSizeTextFromSize:viewModel.size]; 160 | _statusLayer.string = [NSString stringWithFormat:@"%@(%@)", viewModel.statusText, sizeStr]; 161 | } 162 | 163 | - (void)inProgressModel:(RWTransferViewModel *)viewModel { 164 | 165 | [_statusLayer setHidden:YES]; 166 | [_sizeLayer setHidden:NO]; 167 | [_progressLayer setHidden:NO]; 168 | [_progressBgLayer setHidden:NO]; 169 | [_cancelBtn setHidden:NO]; 170 | 171 | NSString *transferSizeStr = [RWCommon getFileSizeTextFromSize:viewModel.transferSize]; 172 | NSString *sizeStr = [RWCommon getFileSizeTextFromSize:viewModel.size]; 173 | _sizeLayer.string = [NSString stringWithFormat:@"%@/%@", transferSizeStr, sizeStr]; 174 | 175 | CGFloat progress = (CGFloat)_viewModel.transferSize / (CGFloat)_viewModel.size; 176 | [self setProgress:progress]; 177 | } 178 | 179 | - (void)setProgress:(CGFloat)progress { 180 | if (progress <= 0) { 181 | self.progressLayer.frame = CGRectMake(spaceWidth + imageWidth + spaceWidth, 35, 0, 2); 182 | }else if (progress <= 1) { 183 | self.progressLayer.frame = CGRectMake(spaceWidth + imageWidth + spaceWidth, 35, progress * _fullProgressWidth, 2); 184 | }else { 185 | self.progressLayer.frame = CGRectMake(spaceWidth + imageWidth + spaceWidth, 35, _fullProgressWidth, 2); 186 | } 187 | } 188 | 189 | 190 | @end 191 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/View/RWTransferListView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferListView.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/22. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RWTransferListView : UIView 12 | 13 | - (void)reloadTableView; 14 | 15 | - (void)reloadSingleCell:(NSInteger)index; 16 | 17 | - (void)reloadProgressCell:(NSInteger)index; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/View/RWTransferListView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferListView.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/22. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWTransferListView.h" 10 | #import "RWTransferListCell.h" 11 | 12 | #import "RWTransferViewModel.h" 13 | #import "RWTransferCenter.h" 14 | 15 | static NSString *const cellId = @"RWTransferListCell"; 16 | @interface RWTransferListView () 17 | 18 | @property (strong, nonatomic)UITableView *tb; 19 | 20 | @property (strong, nonatomic)RWTransferCenter *transferCenter; 21 | 22 | @end 23 | 24 | @implementation RWTransferListView 25 | 26 | #pragma mark - TableView 27 | 28 | -(instancetype)initWithFrame:(CGRect)frame { 29 | self = [super initWithFrame:frame]; 30 | if (self) { 31 | [self setupView]; 32 | } 33 | return self; 34 | } 35 | 36 | -(void)setupView { 37 | [self addSubview:self.tb]; 38 | } 39 | 40 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 41 | return self.transferCenter.allTaskDatas.count; 42 | } 43 | 44 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 45 | RWTransferViewModel *taskViewModel = self.transferCenter.allTaskDatas[indexPath.row]; 46 | RWTransferListCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId forIndexPath:indexPath]; 47 | [cell bindViewModel:taskViewModel]; 48 | return cell; 49 | } 50 | 51 | -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 52 | return 70; 53 | } 54 | 55 | - (void)reloadTableView { 56 | dispatch_async(dispatch_get_main_queue(), ^{ 57 | [self.tb reloadData]; 58 | }); 59 | } 60 | 61 | - (void)reloadSingleCell:(NSInteger)index { 62 | dispatch_async(dispatch_get_main_queue(), ^{ 63 | if (index >= 0) { 64 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; 65 | RWTransferListCell *cell = [self.tb cellForRowAtIndexPath:indexPath]; 66 | RWTransferViewModel *taskModel = self.transferCenter.allTaskDatas[index]; 67 | [cell bindViewModel:taskModel]; 68 | } 69 | }); 70 | } 71 | 72 | - (void)reloadProgressCell:(NSInteger)index { 73 | dispatch_async(dispatch_get_main_queue(), ^{ 74 | if (index >= 0) { 75 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; 76 | RWTransferListCell *cell = [self.tb cellForRowAtIndexPath:indexPath]; 77 | RWTransferViewModel *taskModel = self.transferCenter.allTaskDatas[index]; 78 | [cell inProgressModel:taskModel]; 79 | } 80 | }); 81 | } 82 | 83 | #pragma mark - Lazy load 84 | 85 | -(UITableView *)tb { 86 | if (!_tb) { 87 | _tb = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain]; 88 | _tb.delegate = self; 89 | _tb.dataSource = self; 90 | [_tb registerClass:[RWTransferListCell class] forCellReuseIdentifier:cellId]; 91 | } 92 | return _tb; 93 | } 94 | 95 | -(RWTransferCenter *)transferCenter { 96 | return [RWTransferCenter center]; 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/ViewModel/RWTransferCenter.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferCenter.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/3. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RWPhotoModel,RWTransferViewModel; 12 | @interface RWTransferCenter : NSObject 13 | 14 | @property (strong, nonatomic)NSMutableArray *readyTaskDatas; 15 | 16 | @property (strong, nonatomic)NSMutableArray *allTaskDatas; 17 | 18 | + (instancetype)center; 19 | 20 | - (void)setupReadyTaskDatas:(NSArray *)datas; 21 | 22 | - (RWTransferViewModel *)currentReadyTask; 23 | 24 | - (RWTransferViewModel *)getTaskWithTimestampText:(NSString *)timestampText; 25 | 26 | - (NSInteger)getTaskIndexWithTimestampText:(NSString *)timestampText; 27 | 28 | - (void)nextReadyTask; 29 | 30 | - (void)createReceiveTask:(RWPhotoModel *)model withTimestampText:(NSString *)timestampText; 31 | 32 | - (void)removeAllTaskData; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/ViewModel/RWTransferCenter.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferCenter.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/3. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWTransferCenter.h" 10 | #import "RWTransferViewModel.h" 11 | 12 | static RWTransferCenter *_center = nil; 13 | 14 | @interface RWTransferCenter() 15 | 16 | @end 17 | 18 | @implementation RWTransferCenter 19 | 20 | + (instancetype)center { 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | _center = [[RWTransferCenter alloc] init]; 24 | }); 25 | return _center; 26 | } 27 | 28 | - (void)setupReadyTaskDatas:(NSArray *)datas { 29 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:datas.count]; 30 | for (RWPhotoModel *model in datas) { 31 | RWTransferViewModel *viewModel = [[RWTransferViewModel alloc] initWithModel:model]; 32 | viewModel.source = RWTransferSourceMine; 33 | [array addObject:viewModel]; 34 | RWLog(@"文件名 :%@", viewModel.name); 35 | } 36 | @synchronized(self) { 37 | [self.readyTaskDatas addObjectsFromArray:array]; 38 | [self.allTaskDatas addObjectsFromArray:array]; 39 | } 40 | } 41 | 42 | - (RWTransferViewModel *)currentReadyTask { 43 | RWTransferViewModel *viewModel; 44 | @synchronized(self) { 45 | viewModel = self.readyTaskDatas[0]; 46 | } 47 | return viewModel; 48 | } 49 | 50 | - (RWTransferViewModel *)getTaskWithTimestampText:(NSString *)timestampText { 51 | RWTransferViewModel *viewModel; 52 | NSPredicate *pre = [NSPredicate predicateWithFormat:@"%K CONTAINS %@", @"timestampText", timestampText]; 53 | NSArray *array = [self.allTaskDatas filteredArrayUsingPredicate:pre]; 54 | if (array.count) { 55 | viewModel = array[0]; 56 | } 57 | return viewModel; 58 | } 59 | 60 | - (NSInteger)getTaskIndexWithTimestampText:(NSString *)timestampText { 61 | RWTransferViewModel *viewModel = [self getTaskWithTimestampText:timestampText]; 62 | NSInteger index = -1; 63 | index = [self.allTaskDatas indexOfObject:viewModel]; 64 | return index; 65 | } 66 | 67 | - (void)nextReadyTask { 68 | @synchronized(self) { 69 | [self.readyTaskDatas removeObjectAtIndex:0]; 70 | } 71 | } 72 | 73 | - (void)createReceiveTask:(RWPhotoModel *)model withTimestampText:(NSString *)timestampText{ 74 | RWTransferViewModel *viewModel = [[RWTransferViewModel alloc] initWithModel:model]; 75 | viewModel.source = RWTransferSourceOther; 76 | viewModel.timestampText = timestampText; 77 | [self addTaskToAllList:viewModel]; 78 | } 79 | 80 | - (void)addTaskToAllList:(RWTransferViewModel *)taskModel { 81 | @synchronized(self) { 82 | [self.allTaskDatas addObject:taskModel]; 83 | } 84 | } 85 | 86 | #pragma mark - Remove 87 | - (void)removeAllTaskData { 88 | [self.allTaskDatas removeAllObjects]; 89 | [self.readyTaskDatas removeAllObjects]; 90 | } 91 | 92 | #pragma mark - Lazy load 93 | 94 | - (NSMutableArray *)readyTaskDatas { 95 | if (!_readyTaskDatas) { 96 | _readyTaskDatas = [NSMutableArray array]; 97 | } 98 | return _readyTaskDatas; 99 | } 100 | 101 | - (NSMutableArray *)allTaskDatas { 102 | if (!_allTaskDatas) { 103 | _allTaskDatas = [NSMutableArray array]; 104 | } 105 | return _allTaskDatas; 106 | } 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/ViewModel/RWTransferListViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferListViewModel.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWBaseViewModel.h" 10 | 11 | typedef void(^fromReceiveBeginBlock)(NSDictionary *dict); 12 | typedef void(^fromReceiveProgressBlock)(NSDictionary *dict); 13 | typedef void(^fromReceiveFinishBlock)(NSDictionary *dict); 14 | typedef void(^fromReceiveErrorBlock)(NSDictionary *dict); 15 | typedef void(^fromReceiveCancelBlock)(NSDictionary *dict); 16 | 17 | @interface RWTransferListViewModel : RWBaseViewModel 18 | 19 | - (void)setTarget:(id)target; 20 | 21 | - (NSInteger)getTaskIndexWithStreamName:(NSString *)streamName; 22 | 23 | #pragma mark - Sender 24 | 25 | - (void)sendPeerTaskInfo; 26 | 27 | - (void)nextReadyTask; 28 | 29 | #pragma mark - From Receiver 30 | 31 | - (void)sendTaskBegin:(fromReceiveBeginBlock)beginBlock; 32 | 33 | - (void)sendTaskProgress:(fromReceiveProgressBlock)progressBlock; 34 | 35 | - (void)sendTaskFinish:(fromReceiveFinishBlock)finishBlock; 36 | 37 | - (void)sendTaskError:(fromReceiveErrorBlock)errorBlock; 38 | 39 | #pragma mark - Receiver 40 | 41 | - (void)handleReceiveData:(NSData *)data; 42 | 43 | - (void)createReceiveStreamWithStream:(NSInputStream *)stream streamName:(NSString *)streamName; 44 | 45 | #pragma mark - Tell Sender Task Status 46 | 47 | - (void)receiveTaskProgressWithStreamName:(NSString *)streamName progress:(long long)progress; 48 | 49 | - (void)receiveTaskFinishWithStreamName:(NSString *)streamName; 50 | 51 | - (void)receiveTaskErrorWithStreamName:(NSString *)streamName; 52 | 53 | #pragma mark - Common 54 | 55 | - (void)cancelTaskWithStreamName:(NSString *)streamName; 56 | 57 | - (void)sendTaskCancel:(fromReceiveCancelBlock)cancelBlock; 58 | 59 | #pragma mark - Handler Receive File 60 | 61 | - (void)handleTmpFile:(NSString *)filePath name:(NSString *)name completion:(void(^)(NSString *targetName))completion; 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/ViewModel/RWTransferListViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferListViewModel.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWTransferListViewModel.h" 10 | #import "RWDataTransfer.h" 11 | #import "RWFileManager.h" 12 | #import "RWFileHandle.h" 13 | 14 | #import "RWInputStream.h" 15 | #import "RWOutputStream.h" 16 | 17 | #import "RWUserCenter.h" 18 | #import "RWSession.h" 19 | #import "RWTransferCenter.h" 20 | #import "RWTransferViewModel.h" 21 | 22 | @interface RWTransferListViewModel() 23 | 24 | @property (strong, nonatomic)RWTransferCenter *center; 25 | @property (strong, nonatomic)RWSession *session; 26 | 27 | @property (assign, nonatomic)id target; 28 | 29 | @property (copy, nonatomic)fromReceiveBeginBlock beginBlock; 30 | @property (copy, nonatomic)fromReceiveProgressBlock progressBlock; 31 | @property (copy, nonatomic)fromReceiveFinishBlock finishBlock; 32 | @property (copy, nonatomic)fromReceiveErrorBlock errorBlock; 33 | @property (copy, nonatomic)fromReceiveCancelBlock cancelBlock; 34 | 35 | @end 36 | 37 | @implementation RWTransferListViewModel 38 | 39 | - (MCPeerID *)getFirstPeer { 40 | NSArray *peers = [self.session connectedPeers]; 41 | if (peers.count) { 42 | return peers[0]; 43 | } 44 | return nil; 45 | } 46 | 47 | - (NSInteger)getTaskIndexWithStreamName:(NSString *)streamName { 48 | NSInteger index = [self.center getTaskIndexWithTimestampText:streamName]; 49 | return index; 50 | } 51 | 52 | #pragma mark - Send 53 | 54 | - (void)sendPeerTaskInfo { 55 | RWLog(@"任务准备:%@", self.center.readyTaskDatas); 56 | if (self.center.readyTaskDatas.count <= 0) { 57 | return; 58 | } 59 | RWTransferViewModel *viewModel = [self.center currentReadyTask]; 60 | if (viewModel.status != RWTransferStatusReady) { 61 | RWLog(@"目标任务不在准备状态:%@", viewModel); 62 | return; 63 | } 64 | MCPeerID *peer = [self getFirstPeer]; 65 | if (peer) { 66 | NSData *data = [viewModel getTaskData]; 67 | [self.session sendData:data toPeers:@[peer]]; 68 | } 69 | 70 | viewModel.status = RWTransferStatusPrepare; 71 | } 72 | 73 | - (void)createSendStreamWithTarget:(id)target task:(RWTransferViewModel *)taskModel { 74 | if (self.center.readyTaskDatas.count <= 0) { 75 | return; 76 | } 77 | if (taskModel.status != RWTransferStatusPrepare) { 78 | RWLog(@"目标任务不在准备状态:%@", taskModel); 79 | return; 80 | } 81 | RWLog(@"准备发送 %@", taskModel.timestampText); 82 | NSArray *peers = [self.session connectedPeers]; 83 | 84 | if (peers.count) { 85 | RWOutputStream *outputStream = [[RWOutputStream alloc] initWithOutputStream:[self.session outputStreamForPeer:peers[0] With:taskModel.timestampText]]; 86 | outputStream.streamName = taskModel.timestampText; 87 | outputStream.delegate = target; 88 | [outputStream streamWithAsset:taskModel.asset fileType:taskModel.fileType]; 89 | [outputStream start]; 90 | 91 | taskModel.status = RWTransferStatusTransfer; 92 | taskModel.outputStream = outputStream; 93 | } 94 | } 95 | 96 | - (void)nextReadyTask { 97 | [self.center nextReadyTask]; 98 | } 99 | 100 | - (void)sendTaskCurrentProgressUpdate:(NSDictionary *)dict { 101 | 102 | NSString *timestampText = dict[@"timestamp"]; 103 | long long progress = [dict[@"progress"] longLongValue]; 104 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:timestampText]; 105 | taskViewModel.transferSize = progress; 106 | 107 | if (_progressBlock) { 108 | _progressBlock(dict); 109 | } 110 | } 111 | 112 | - (void)sendTaskFinishUpdate:(NSDictionary *)dict { 113 | 114 | NSString *timestampText = dict[@"timestamp"]; 115 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:timestampText]; 116 | taskViewModel.status = RWTransferStatusFinish; 117 | 118 | if (_finishBlock) { 119 | _finishBlock(dict); 120 | } 121 | } 122 | 123 | - (void)sendTaskErrorUpdate:(NSDictionary *)dict { 124 | 125 | NSString *timestampText = dict[@"timestamp"]; 126 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:timestampText]; 127 | taskViewModel.status = RWTransferStatusError; 128 | 129 | if (_errorBlock) { 130 | _errorBlock(dict); 131 | } 132 | } 133 | 134 | #pragma mark - From Receiver 135 | 136 | - (void)sendTaskBegin:(fromReceiveBeginBlock)beginBlock { 137 | _beginBlock = beginBlock; 138 | } 139 | 140 | - (void)sendTaskProgress:(fromReceiveProgressBlock)progressBlock { 141 | _progressBlock = progressBlock; 142 | } 143 | 144 | - (void)sendTaskFinish:(fromReceiveFinishBlock)finishBlock { 145 | _finishBlock = finishBlock; 146 | } 147 | 148 | - (void)sendTaskError:(fromReceiveErrorBlock)errorBlock { 149 | _errorBlock = errorBlock; 150 | } 151 | 152 | #pragma mark - Receive 153 | 154 | - (void)handleReceiveData:(NSData *)data { 155 | NSDictionary *dict = [RWDataTransfer dataToDictionary:data]; 156 | RWLog(@"收到数据 %@", dict); 157 | RWTransferDataType dataType = [dict[@"dataType"] integerValue]; 158 | NSDictionary *dataDic = dict[@"data"]; 159 | switch (dataType) { 160 | case RWTransferDataTypeSendTaskInfo: 161 | { 162 | [self receiveTaskInfo:dataDic]; 163 | break; 164 | } 165 | case RWTransferDataTypeReceiveTaskInfo: 166 | { 167 | [self receiveTaskInfoCallback:dataDic]; 168 | break; 169 | } 170 | case RWTransferDataTypeReceiveProgress: 171 | { 172 | [self sendTaskCurrentProgressUpdate:dataDic]; 173 | break; 174 | } 175 | case RWTransferDataTypeFinish: 176 | { 177 | RWStatus(@"接收方接收完成"); 178 | [self sendTaskFinishUpdate:dataDic]; 179 | break; 180 | } 181 | case RWTransferDataTypeError: 182 | { 183 | RWStatus(@"接收方报错"); 184 | [self sendTaskErrorUpdate:dataDic]; 185 | break; 186 | } 187 | 188 | default: 189 | break; 190 | } 191 | } 192 | 193 | - (void)createReceiveStreamWithStream:(NSInputStream *)stream streamName:(NSString *)streamName { 194 | RWInputStream *inputStream = [[RWInputStream alloc] initWithInputStream:stream]; 195 | inputStream.streamName = streamName; 196 | inputStream.delegate = _target; 197 | [inputStream start]; 198 | 199 | //任务进入传输状态 200 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:streamName]; 201 | taskViewModel.status = RWTransferStatusTransfer; 202 | taskViewModel.inputStream = inputStream; 203 | } 204 | 205 | #pragma mark - Handle Receive Data 206 | - (void)receiveTaskInfo:(NSDictionary *)data { 207 | RWStatus(@"接收到发送者发来的任务信息 %@", data); 208 | RWPhotoModel *model = [[RWPhotoModel alloc] initWithDictionary:data]; 209 | NSString *timestampText = data[@"timestamp"]; 210 | [self.center createReceiveTask:model withTimestampText:timestampText]; 211 | 212 | NSDictionary *dict = @{ 213 | @"dataType":@(RWTransferDataTypeReceiveTaskInfo), 214 | @"data":@{ 215 | @"timestamp":timestampText 216 | } 217 | }; 218 | [self sendDataWithDictionary:dict]; 219 | 220 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:timestampText]; 221 | taskViewModel.status = RWTransferStatusPrepare; 222 | } 223 | 224 | - (void)receiveTaskInfoCallback:(NSDictionary *)data { 225 | NSString *timestampText = data[@"timestamp"]; 226 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:timestampText]; 227 | if (taskViewModel) { 228 | [self createSendStreamWithTarget:_target task:taskViewModel]; 229 | !_beginBlock?:_beginBlock(data); 230 | } 231 | 232 | RWStatus(@"发送者收到接收者通知具体哪个任务 %@", timestampText); 233 | } 234 | 235 | #pragma mark - Tell Sender Task Status 236 | - (void)receiveTaskProgressWithStreamName:(NSString *)streamName progress:(long long)progress { 237 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:streamName]; 238 | taskViewModel.transferSize = progress; 239 | 240 | NSDictionary *dict = @{ 241 | @"dataType":@(RWTransferDataTypeReceiveProgress), 242 | @"data":@{ 243 | @"timestamp":streamName, 244 | @"progress":@(progress) 245 | } 246 | }; 247 | [self sendDataWithDictionary:dict]; 248 | } 249 | 250 | - (void)receiveTaskFinishWithStreamName:(NSString *)streamName { 251 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:streamName]; 252 | taskViewModel.status = RWTransferStatusFinish; 253 | 254 | NSDictionary *dict = @{ 255 | @"dataType":@(RWTransferDataTypeFinish), 256 | @"data":@{ 257 | @"timestamp":streamName, 258 | } 259 | }; 260 | [self sendDataWithDictionary:dict]; 261 | } 262 | 263 | - (void)receiveTaskErrorWithStreamName:(NSString *)streamName { 264 | NSDictionary *dict = @{ 265 | @"dataType":@(RWTransferDataTypeError), 266 | @"data":@{ 267 | @"timestamp":streamName, 268 | } 269 | }; 270 | [self sendDataWithDictionary:dict]; 271 | } 272 | 273 | - (void)sendDataWithDictionary:(NSDictionary *)dict { 274 | MCPeerID *peer = [self getFirstPeer]; 275 | if (peer) { 276 | NSData *data = [RWDataTransfer dictionaryToData:dict]; 277 | [self.session sendData:data toPeers:@[peer]]; 278 | } 279 | } 280 | 281 | #pragma mark - Common 282 | 283 | - (void)cancelTaskWithStreamName:(NSString *)streamName { 284 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:streamName]; 285 | taskViewModel.status = RWTransferStatusCancel; 286 | [taskViewModel.outputStream stop]; 287 | [taskViewModel.inputStream stop]; 288 | 289 | NSDictionary *dict = @{ 290 | @"dataType":@(RWTransferDataTypeCancel), 291 | @"data":@{ 292 | @"timestamp":streamName, 293 | } 294 | }; 295 | [self sendDataWithDictionary:dict]; 296 | } 297 | 298 | - (void)sendTaskCancel:(fromReceiveCancelBlock)cancelBlock { 299 | _cancelBlock = cancelBlock; 300 | } 301 | 302 | - (void)sendTaskCancelUpdate:(NSDictionary *)dict { 303 | NSString *timestampText = dict[@"timestamp"]; 304 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:timestampText]; 305 | taskViewModel.status = RWTransferStatusCancel; 306 | 307 | if (_cancelBlock) { 308 | _cancelBlock(dict); 309 | } 310 | } 311 | 312 | #pragma mark - Handler Receive File 313 | 314 | - (void)handleTmpFile:(NSString *)filePath name:(NSString *)name completion:(void(^)(NSString *targetName))completion { 315 | RWLog(@"传输完成 得到临时文件路径 : %@ |||| %@", filePath, name); 316 | 317 | __weak typeof(self) weakSelf = self; 318 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 319 | RWTransferViewModel *taskViewModel = [self.center getTaskWithTimestampText:name]; 320 | NSString *fileDirectory; 321 | if ([taskViewModel.fileType isEqualToString:kFileTypePicture]) { 322 | fileDirectory = [RWFileManager picturesDirectory]; 323 | } else if ([taskViewModel.fileType isEqualToString:kFileTypeVideo]) { 324 | fileDirectory = [RWFileManager videosDirectory]; 325 | } 326 | NSString *fileName = taskViewModel.name; 327 | NSString *targetPath = [NSString stringWithFormat:@"%@/%@", fileDirectory, fileName]; 328 | [weakSelf moveItemFrom:filePath to:targetPath]; 329 | taskViewModel.sandboxPath = targetPath; 330 | 331 | dispatch_async(dispatch_get_main_queue(), ^{ 332 | !completion?:completion(name); 333 | }); 334 | }); 335 | } 336 | 337 | - (void)moveItemFrom:(NSString *)filePath to:(NSString *)targetPath { 338 | [RWFileHandle copyFileFromPath:filePath toPath:targetPath]; 339 | [RWFileManager deleteFileAtPath:filePath]; 340 | } 341 | 342 | #pragma mark - Lazy load 343 | 344 | -(RWSession *)session { 345 | return [RWUserCenter center].session; 346 | } 347 | 348 | -(RWTransferCenter *)center { 349 | return [RWTransferCenter center]; 350 | } 351 | 352 | @end 353 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/ViewModel/RWTransferViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferViewModel.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/2. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RWPhotoModel.h" 11 | #import 12 | 13 | typedef NS_ENUM(NSUInteger, RWTransferStatus) { 14 | RWTransferStatusReady = 0, //任务初始状态 15 | RWTransferStatusPrepare, //任务准备状态 个别任务在发送前需要时间进行特别处理 16 | RWTransferStatusTransfer, //任务传输状态 17 | RWTransferStatusFinish, //任务完成状态 18 | 19 | RWTransferStatusCancel = 99, //任务取消状态 20 | RWTransferStatusError = 999, //任务错误状态 21 | }; 22 | 23 | typedef NS_ENUM(NSUInteger, RWTransferSource) { 24 | RWTransferSourceNone, 25 | RWTransferSourceMine, 26 | RWTransferSourceOther, 27 | }; 28 | 29 | @class RWOutputStream, RWInputStream; 30 | @interface RWTransferViewModel : NSObject 31 | 32 | @property (copy, nonatomic, readonly)NSString *name; 33 | 34 | @property (copy, nonatomic, readonly)PHAsset *asset; 35 | 36 | @property (copy, nonatomic)NSString *sandboxPath; 37 | 38 | @property (assign, nonatomic)long long size; 39 | 40 | @property (assign, nonatomic)long long transferSize; 41 | 42 | @property (assign, nonatomic, readonly)float progressValue; 43 | 44 | @property (assign, nonatomic)RWTransferStatus status; 45 | 46 | @property (copy, nonatomic, readonly)NSString *statusText; 47 | 48 | @property (assign, nonatomic)RWTransferSource source; 49 | 50 | @property (copy, nonatomic)NSString *timestampText; 51 | 52 | @property (copy, nonatomic, readonly)NSString *fileType; 53 | 54 | @property (copy, nonatomic, readonly)NSString *pathExtension; 55 | 56 | @property (strong, nonatomic)UIImage *cover; 57 | 58 | //*******************传输任务对应的流 Start********************// 59 | 60 | @property (strong, nonatomic)RWOutputStream *outputStream; 61 | 62 | @property (strong, nonatomic)RWInputStream *inputStream; 63 | 64 | //*******************传输任务对应的流 End********************// 65 | 66 | - (instancetype) initWithModel:(RWPhotoModel *)model; 67 | 68 | - (NSData *)getTaskData; 69 | 70 | - (void)loadImageDataWithPhotoWidth:(CGFloat)photoWidth success:(void(^)(id))success failure:(void (^)(NSError *))failure; 71 | 72 | - (void)loadSandBoxImageWithIsCoverSize:(BOOL)isCoverSize completion:(void(^)(UIImage *coverImage))completion; 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Transfer/ViewModel/RWTransferViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWTransferViewModel.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/2. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWTransferViewModel.h" 10 | 11 | #import "RWDataTransfer.h" 12 | #import "RWImageLoad.h" 13 | #import "RWFileManager.h" 14 | 15 | #import "UIImage+Resize.h" 16 | #import "UIImage+Video.h" 17 | 18 | #import "RWOutputStream.h" 19 | #import "RWInputStream.h" 20 | 21 | static NSString *const RWTransferStatusReadyText = @"初始化"; 22 | static NSString *const RWTransferStatusPrepareText = @"准备中"; 23 | static NSString *const RWTransferStatusTransferText = @"传输中"; 24 | static NSString *const RWTransferStatusFinishText = @"完成"; 25 | static NSString *const RWTransferStatusCancelText = @"取消"; 26 | static NSString *const RWTransferStatusErrorText = @"错误"; 27 | 28 | @interface RWTransferViewModel() 29 | 30 | @property (copy, nonatomic)RWPhotoModel *model; 31 | 32 | @property (copy, nonatomic, readwrite)NSString *name; 33 | 34 | @property (copy, nonatomic, readwrite)PHAsset *asset; 35 | 36 | @property (assign, nonatomic, readwrite)float progressValue; 37 | 38 | @property (copy, nonatomic, readwrite)NSString *statusText; 39 | 40 | @property (copy, nonatomic, readwrite)NSString *fileType; 41 | 42 | @property (copy, nonatomic, readwrite)NSString *pathExtension; 43 | 44 | @end 45 | 46 | @implementation RWTransferViewModel 47 | 48 | - (instancetype) initWithModel:(RWPhotoModel *)model { 49 | self = [super init]; 50 | if (self) { 51 | _model = model; 52 | _name = model.name; 53 | _asset = model.asset; 54 | _size = model.size; 55 | _progressValue = 0.0; 56 | _status = RWTransferStatusReady; 57 | _statusText = RWTransferStatusReadyText; 58 | _source = RWTransferSourceNone; 59 | _timestampText = [self getDefaultTimestamp]; 60 | _fileType = model.fileType; 61 | _pathExtension = model.pathExtension; 62 | } 63 | return self; 64 | } 65 | 66 | - (NSData *)getTaskData { 67 | __weak typeof(self) weakSelf = self; 68 | NSLock *lock = [[NSLock alloc] init]; 69 | __block NSInteger tag = 0; 70 | while(true) { 71 | [lock lock]; 72 | if (tag != 0) { 73 | break; 74 | } 75 | if ([_fileType isEqualToString:kFileTypeVideo]) { 76 | [[RWImageLoad shareLoad] getVideoInfoWithAsset:_asset completion:^(long long size, UIImage *image) { 77 | weakSelf.size = size; 78 | tag = 1; 79 | [lock unlock]; 80 | }]; 81 | } else if ([_fileType isEqualToString:kFileTypePicture]) { 82 | [[RWImageLoad shareLoad] getPhotoDataWithAsset:_asset completion:^(NSData *imageData, NSString *dataUTI, NSDictionary *info) { 83 | weakSelf.size = imageData.length; 84 | tag = 1; 85 | [lock unlock]; 86 | }]; 87 | } 88 | 89 | } 90 | 91 | NSDictionary *dict = @{ 92 | @"dataType":@(RWTransferDataTypeSendTaskInfo), 93 | @"data":@{ 94 | @"name":_name, 95 | @"size":@(_size), 96 | @"timestamp":_timestampText, 97 | @"fileType":_fileType, 98 | @"pathExtension":_pathExtension 99 | } 100 | }; 101 | 102 | NSData *data = [RWDataTransfer dictionaryToData:dict]; 103 | return data; 104 | } 105 | 106 | - (void)setStatus:(RWTransferStatus)status { 107 | _status = status; 108 | switch (status) { 109 | case RWTransferStatusReady: 110 | _statusText = RWTransferStatusReadyText; 111 | break; 112 | 113 | case RWTransferStatusPrepare: 114 | _statusText = RWTransferStatusPrepareText; 115 | break; 116 | 117 | case RWTransferStatusTransfer: 118 | _statusText = RWTransferStatusTransferText; 119 | break; 120 | 121 | case RWTransferStatusFinish: 122 | _statusText = RWTransferStatusFinishText; 123 | break; 124 | 125 | case RWTransferStatusCancel: 126 | _statusText = RWTransferStatusCancelText; 127 | break; 128 | 129 | case RWTransferStatusError: 130 | _statusText = RWTransferStatusErrorText; 131 | break; 132 | 133 | default: 134 | break; 135 | } 136 | } 137 | 138 | - (void)loadImageDataWithPhotoWidth:(CGFloat)photoWidth success:(void(^)(id))success failure:(void (^)(NSError *))failure { 139 | 140 | __weak typeof(self) weakSelf = self; 141 | [[RWImageLoad shareLoad] getPhotoWithAsset:_asset photoWidth:photoWidth completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { 142 | weakSelf.cover = photo; 143 | !success?:success(photo); 144 | } progressHandler:nil networkAccessAllowed:NO]; 145 | } 146 | 147 | - (void)loadSandBoxImageWithIsCoverSize:(BOOL)isCoverSize completion:(void(^)(UIImage *coverImage))completion { 148 | 149 | if ([RWFileManager fileExistsAtPath:_sandboxPath]) { 150 | UIImage *cover = nil; 151 | if ([_fileType isEqualToString:kFileTypePicture]) { 152 | cover = [UIImage imageWithContentsOfFile:_sandboxPath]; 153 | } else if ([_fileType isEqualToString:kFileTypeVideo]) { 154 | cover = [UIImage getThumbnailImageWithFilePath:_sandboxPath]; 155 | } 156 | if (isCoverSize) { 157 | cover = [UIImage imageWithImageSimple:cover scaledToSize:CoverSize]; 158 | _cover = cover; 159 | } 160 | !completion?:completion(cover); 161 | } 162 | } 163 | 164 | - (NSString *)getDefaultTimestamp { 165 | NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970]; 166 | NSString *timestampText = [NSString stringWithFormat:@"%f", timestamp]; 167 | return timestampText; 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Wait/Controller/WaitViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WaitViewController.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WaitViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Wait/Controller/WaitViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WaitViewController.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "WaitViewController.h" 10 | #import "RWBrowser.h" 11 | #import "RWSession.h" 12 | #import "RWUserCenter.h" 13 | #import "TransferListViewController.h" 14 | 15 | @interface WaitViewController () 16 | 17 | @end 18 | 19 | @implementation WaitViewController 20 | 21 | - (void)viewDidDisappear:(BOOL)animated{ 22 | [super viewDidDisappear:animated]; 23 | 24 | [[RWBrowser shareInstance] stopWait]; 25 | 26 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 27 | } 28 | 29 | - (void)viewWillAppear:(BOOL)animated { 30 | [super viewWillAppear:animated]; 31 | 32 | [[RWUserCenter center].session.session disconnect]; 33 | 34 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connect) name:kRWSessionStateConnectedNotification object:nil]; 35 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notConnect) name:kRWSessionStateNotConnectedNotification object:nil]; 36 | } 37 | 38 | - (void)viewDidLoad { 39 | [super viewDidLoad]; 40 | 41 | self.title = @"等待连接"; 42 | 43 | NSString *deviceName = [UIDevice currentDevice].name; 44 | [[RWBrowser shareInstance] setConfigurationWithName:deviceName Identifier:@"rw"]; 45 | 46 | [[RWBrowser shareInstance] startWaitForConnect]; 47 | } 48 | 49 | - (void)connect { 50 | dispatch_async(dispatch_get_main_queue(), ^{ 51 | RWTransferListViewModel *vm = [[RWTransferListViewModel alloc] init]; 52 | vm.title = @"传输圈"; 53 | TransferListViewController *vc = [[TransferListViewController alloc] initWithViewModel:vm]; 54 | [self.navigationController pushViewController:vc animated:YES]; 55 | }); 56 | } 57 | 58 | - (void)notConnect { 59 | dispatch_async(dispatch_get_main_queue(), ^{ 60 | [self.navigationController popViewControllerAnimated:YES]; 61 | }); 62 | } 63 | 64 | - (void)didReceiveMemoryWarning { 65 | [super didReceiveMemoryWarning]; 66 | // Dispose of any resources that can be recreated. 67 | } 68 | 69 | /* 70 | #pragma mark - Navigation 71 | 72 | // In a storyboard-based application, you will often want to do a little preparation before navigation 73 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 74 | // Get the new view controller using [segue destinationViewController]. 75 | // Pass the selected object to the new view controller. 76 | } 77 | */ 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Section/Wait/Controller/WaitViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Tool/RWDataTransfer/RWDataTransfer.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWDataTransfer.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RWDataTransfer : NSObject 12 | 13 | + (NSData *)dictionaryToData:(NSDictionary *)dict; 14 | 15 | + (NSDictionary *)dataToDictionary:(NSData *)data; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Tool/RWDataTransfer/RWDataTransfer.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWDataTransfer.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWDataTransfer.h" 10 | 11 | @implementation RWDataTransfer 12 | 13 | + (NSData *)dictionaryToData:(NSDictionary *)dict { 14 | NSError *error; 15 | NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&error]; 16 | if (error) { 17 | RWError(@"%@", [error userInfo].description); 18 | } 19 | return data; 20 | } 21 | 22 | + (NSDictionary *)dataToDictionary:(NSData *)data { 23 | NSError *error; 24 | NSDictionary *dictionary =[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error]; 25 | if (error) { 26 | RWError(@"%@", [error userInfo].description); 27 | } 28 | return dictionary; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Tool/RWFileHandle/RWFileHandle.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWFileHandle.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/8. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RWFileHandle : NSObject 12 | 13 | + (void)copyFileFromPath:(NSString *)path1 toPath:(NSString *)path2; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Tool/RWFileHandle/RWFileHandle.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWFileHandle.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/8. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWFileHandle.h" 10 | #import "RWFileManager.h" 11 | 12 | @implementation RWFileHandle 13 | 14 | + (void)copyFileFromPath:(NSString *)path1 toPath:(NSString *)path2 15 | { 16 | NSFileHandle * fh1 = [NSFileHandle fileHandleForReadingAtPath:path1];//读到内存 17 | [[NSFileManager defaultManager] createFileAtPath:path2 contents:nil attributes:nil];//写之前必须有该文件 18 | NSFileHandle * fh2 = [NSFileHandle fileHandleForWritingAtPath:path2];//写到文件 19 | NSData * _data = nil; 20 | unsigned long long ret = [fh1 seekToEndOfFile];//返回文件大小 21 | if (ret < 1024 * 1024 * 5) {//小于5M的文件一次读写 22 | [fh1 seekToFileOffset:0]; 23 | _data = [fh1 readDataToEndOfFile]; 24 | [fh2 writeData:_data]; 25 | }else{ 26 | NSUInteger n = ret / (1024 * 1024 * 5); 27 | if (ret % (1024 * 1024 * 5) != 0) { 28 | n++; 29 | } 30 | NSUInteger offset = 0; 31 | NSUInteger size = 1024 * 1024 * 5; 32 | for (NSUInteger i = 0; i < n - 1; i++) { 33 | //大于5M的文件多次读写 34 | [fh1 seekToFileOffset:offset]; 35 | @autoreleasepool { 36 | /*该自动释放池必须要有否则内存一会就爆了 37 | 原因在于readDataOfLength方法返回了一个自动释放的对象,它只能在遇到自动释放池的时候才释放.如果不手动写这个自动释放池,会导致_data指向的对象不能及时释放,最终导致内存爆了. 38 | */ 39 | _data = [fh1 readDataOfLength:size]; 40 | [fh2 seekToEndOfFile]; 41 | [fh2 writeData:_data]; 42 | NSLog(@"%lu/%lu", i + 1, n - 1); 43 | } 44 | offset += size; 45 | } 46 | //最后一次剩余的字节 47 | [fh1 seekToFileOffset:offset]; 48 | _data = [fh1 readDataToEndOfFile]; 49 | [fh2 seekToEndOfFile]; 50 | [fh2 writeData:_data]; 51 | } 52 | [fh1 closeFile]; 53 | [fh2 closeFile]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Tool/RWFileManager/RWFileManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWFileManager.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/8. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | static NSString *const PictureDirectoryName = @"pictures"; 12 | 13 | static NSString *const VideoDirectoryName = @"videos"; 14 | 15 | @interface RWFileManager : NSObject 16 | 17 | 18 | + (NSString *)picturesDirectory; 19 | 20 | + (NSString *)videosDirectory; 21 | 22 | + (NSString *)documentPath; 23 | 24 | + (NSString *)cachePath; 25 | 26 | + (NSString *)tmpPath; 27 | 28 | 29 | + (BOOL)fileExistsAtPath:(NSString *)path; 30 | 31 | + (BOOL)createBlankFileAtPath:(NSString *)path; 32 | 33 | + (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)data; 34 | 35 | + (BOOL)createDirectoryAtPath:(NSString *)path; 36 | 37 | + (BOOL)deleteFileAtPath:(NSString *)path; 38 | 39 | + (NSArray *)subPathAtPath:(NSString *)path; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Tool/RWFileManager/RWFileManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWFileManager.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/3/8. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWFileManager.h" 10 | 11 | @implementation RWFileManager 12 | 13 | + (NSString *)picturesDirectory { 14 | NSString *picturesDirectory = [[self documentPath] stringByAppendingFormat:@"/%@", PictureDirectoryName]; 15 | if (![self fileExistsAtPath:picturesDirectory]) { 16 | [self createDirectoryAtPath:picturesDirectory]; 17 | } 18 | return picturesDirectory; 19 | } 20 | 21 | + (NSString *)videosDirectory { 22 | NSString *videosDirectory = [[self documentPath] stringByAppendingFormat:@"/%@", VideoDirectoryName]; 23 | if (![self fileExistsAtPath:videosDirectory]) { 24 | [self createDirectoryAtPath:videosDirectory]; 25 | } 26 | return videosDirectory; 27 | } 28 | 29 | + (NSString *)documentPath { 30 | return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 31 | } 32 | 33 | + (NSString *)cachePath { 34 | return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; 35 | } 36 | 37 | + (NSString *)tmpPath { 38 | return NSTemporaryDirectory(); 39 | } 40 | 41 | 42 | 43 | + (BOOL)fileExistsAtPath:(NSString *)path { 44 | return [[NSFileManager defaultManager] fileExistsAtPath:path]; 45 | } 46 | 47 | + (BOOL)createBlankFileAtPath:(NSString *)path { 48 | return [self createFileAtPath:path contents:nil]; 49 | } 50 | 51 | + (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)data{ 52 | return [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil]; 53 | } 54 | 55 | + (BOOL)createDirectoryAtPath:(NSString *)path { 56 | NSError *error; 57 | BOOL isSuccess = [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]; 58 | if (error) { 59 | NSLog(@"createDirectoryError : %@", error); 60 | } 61 | return isSuccess; 62 | } 63 | 64 | + (BOOL)deleteFileAtPath:(NSString *)path { 65 | NSError *error; 66 | BOOL isSuccess = [[NSFileManager defaultManager] removeItemAtPath:path error:&error]; 67 | if (error) { 68 | NSLog(@"deleteFileError : %@", error); 69 | } 70 | return isSuccess; 71 | } 72 | 73 | + (NSArray *)subPathAtPath:(NSString *)path { 74 | NSError *error; 75 | NSArray *array = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:path error:&error]; 76 | if (error) { 77 | NSLog(@"subpathsOfDirectoryError : %@", error); 78 | } 79 | return array; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Tool/RWImageLoad/RWImageLoad.h: -------------------------------------------------------------------------------- 1 | // 2 | // RWImageLoad.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/26. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | @interface RWImageLoad : NSObject 14 | 15 | +(instancetype)shareLoad; 16 | 17 | - (void)getAlbumContentImage:(BOOL)contentImage contentVideo:(BOOL)contentVideo completion:(void (^)(NSMutableArray *albums))completion; 18 | 19 | - (PHImageRequestID)getPhotoWithAsset:(id)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed; 20 | 21 | - (PHImageRequestID)getPhotoDataWithAsset:(id)asset completion:(void (^)(NSData *imageData, NSString *dataUTI, NSDictionary * info))completion; 22 | 23 | - (PHImageRequestID)getVideoInfoWithAsset:(id)asset completion:(void (^)(long long size, UIImage *image))completion; 24 | 25 | - (PHImageRequestID)getVideoDataWithAsset:(id)asset completion:(void (^)(NSData *data))completion; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /RWFileTransferDemo/Tool/RWImageLoad/RWImageLoad.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWImageLoad.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/2/26. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "RWImageLoad.h" 10 | #import "RWAlbumModel.h" 11 | #import "UIImage+Resize.h" 12 | #import "UIImage+Video.h" 13 | 14 | static RWImageLoad *_instance = nil; 15 | @implementation RWImageLoad 16 | 17 | + (instancetype)shareLoad { 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | _instance = [[RWImageLoad alloc] init]; 21 | }); 22 | return _instance; 23 | } 24 | 25 | - (void)getAlbumContentImage:(BOOL)contentImage contentVideo:(BOOL)contentVideo completion:(void (^)(NSMutableArray *albums))completion 26 | { 27 | 28 | PHFetchOptions *option = [[PHFetchOptions alloc] init]; 29 | if (!contentVideo) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage]; 30 | if (!contentImage) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", 31 | PHAssetMediaTypeVideo]; 32 | option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]]; 33 | 34 | PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil]; 35 | 36 | __block NSMutableArray *albums = [[NSMutableArray alloc] initWithCapacity:smartAlbums.count]; 37 | 38 | if (!contentVideo) { 39 | for (PHAssetCollection *collection in smartAlbums) 40 | { 41 | if (![collection isKindOfClass:[PHAssetCollection class]]) continue; // 有可能是PHCollectionList类的的对象,过滤掉 42 | PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:option]; 43 | if (fetchResult.count > 0) { 44 | RWAlbumModel *model = [self modelWithResult:fetchResult name:collection.localizedTitle]; 45 | model.fileType = kFileTypePicture; 46 | [albums addObject:model]; 47 | } 48 | } 49 | } 50 | 51 | // 获得相机胶卷 52 | PHAssetCollection *cameraRoll = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil].lastObject; 53 | PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:cameraRoll options:option]; 54 | if (fetchResult.count > 0) { 55 | RWAlbumModel *model = [self modelWithResult:fetchResult name:cameraRoll.localizedTitle]; 56 | if (!contentVideo) { 57 | model.fileType = kFileTypePicture; 58 | } else { 59 | model.fileType = kFileTypeVideo; 60 | } 61 | [albums insertObject:model atIndex:0]; 62 | } 63 | 64 | if (completion) { 65 | completion(albums); 66 | } 67 | } 68 | 69 | - (RWAlbumModel *)modelWithResult:(PHFetchResult*)result name:(NSString *)name { 70 | RWAlbumModel *album = [[RWAlbumModel alloc] init]; 71 | album.title = name; 72 | album.result = result; 73 | album.count = result.count; 74 | return album; 75 | } 76 | 77 | - (PHImageRequestID)getPhotoWithAsset:(id)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed 78 | { 79 | 80 | PHImageManager *manger = [PHImageManager defaultManager]; 81 | int32_t imageRequestID = [manger requestImageForAsset:asset targetSize:CGSizeMake(([[UIScreen mainScreen] bounds].size.width-15)/3, ([[UIScreen mainScreen] bounds].size.width-15)/3) contentMode:PHImageContentModeAspectFit options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { 82 | completion(result,info,true); 83 | }]; 84 | return imageRequestID; 85 | } 86 | 87 | - (PHImageRequestID)getPhotoDataWithAsset:(id)asset completion:(void (^)(NSData *imageData, NSString *dataUTI, NSDictionary * info))completion { 88 | 89 | PHImageManager *manger = [PHImageManager defaultManager]; 90 | PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init]; 91 | option.synchronous = YES; 92 | int32_t imageRequestID = [manger requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { 93 | UIImage* image = [UIImage imageWithData:imageData]; 94 | imageData = UIImageJPEGRepresentation(image, 1.0); 95 | 96 | !completion?:completion(imageData, dataUTI, info); 97 | 98 | }]; 99 | return imageRequestID; 100 | } 101 | 102 | - (PHImageRequestID)getVideoInfoWithAsset:(id)asset completion:(void (^)(long long size, UIImage *image))completion { 103 | return [self getVideoInfo:YES AndData:NO WithAsset:asset completion:^(long long size, UIImage *image, NSData *data) { 104 | !completion?:completion(size, image); 105 | }]; 106 | } 107 | 108 | - (PHImageRequestID)getVideoDataWithAsset:(id)asset completion:(void (^)(NSData *data))completion { 109 | return [self getVideoInfo:NO AndData:YES WithAsset:asset completion:^(long long size, UIImage *image, NSData *data) { 110 | !completion?:completion(data); 111 | }]; 112 | } 113 | 114 | - (PHImageRequestID)getVideoInfo:(BOOL)returnInfo AndData:(BOOL)returnData WithAsset:(id)asset completion:(void (^)(long long size, UIImage *image, NSData *data))completion { 115 | PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init]; 116 | options.version = PHVideoRequestOptionsVersionOriginal; 117 | int32_t imageRequestID = [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) { 118 | if ([asset isKindOfClass:[AVURLAsset class]]) { 119 | AVURLAsset* urlAsset = (AVURLAsset*)asset; 120 | if (returnInfo) { 121 | NSNumber *size; 122 | [urlAsset.URL getResourceValue:&size forKey:NSURLFileSizeKey error:nil]; 123 | 124 | UIImage *image = [UIImage getThumbnailImageWithURLAsset:urlAsset]; 125 | image = [UIImage imageWithImageSimple:image scaledToSize:CoverSize]; 126 | !completion?:completion([size longLongValue], image, nil); 127 | } 128 | 129 | if (returnData) { 130 | NSError *error = nil; 131 | NSData *data = [NSData dataWithContentsOfURL:urlAsset.URL options:NSDataReadingMappedIfSafe error:&error]; 132 | if (error) { 133 | NSLog(@"视频错误 :%@", error); 134 | } 135 | NSLog(@"data length %lu", (unsigned long)data.length); 136 | !completion?:completion(0, nil, data); 137 | } 138 | } 139 | }]; 140 | return imageRequestID; 141 | } 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /RWFileTransferDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /RWFileTransferDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "SearchViewController.h" 11 | #import "WaitViewController.h" 12 | #import "RWAlbumListViewController.h" 13 | #import "RWVideoListViewController.h" 14 | #import "RWAlbumListViewModel.h" 15 | #import "RWBrowser.h" 16 | 17 | 18 | #import "RWImageLoad.h" 19 | 20 | @interface ViewController () 21 | 22 | @end 23 | 24 | @implementation ViewController 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | } 29 | 30 | 31 | - (void)didReceiveMemoryWarning { 32 | [super didReceiveMemoryWarning]; 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | - (IBAction)sendAction:(id)sender { 37 | SearchViewController *vc = [[SearchViewController alloc] init]; 38 | [self.navigationController pushViewController:vc animated:YES]; 39 | } 40 | - (IBAction)receiveAction:(id)sender { 41 | WaitViewController *vc = [[WaitViewController alloc] init]; 42 | [self.navigationController pushViewController:vc animated:YES]; 43 | } 44 | - (IBAction)checkAction:(id)sender { 45 | RWAlbumListViewModel *viewModel = [[RWAlbumListViewModel alloc] init]; 46 | // viewModel.title = @"选择相册"; 47 | // RWAlbumListViewController *vc = [[RWAlbumListViewController alloc] initWithViewModel:viewModel]; 48 | viewModel.title = @"选择视频"; 49 | RWVideoListViewController *vc = [[RWVideoListViewController alloc] initWithViewModel:viewModel]; 50 | [self.navigationController pushViewController:vc animated:YES]; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /RWFileTransferDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // RWFileTransferDemo 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. 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 | -------------------------------------------------------------------------------- /RWFileTransferDemoTests/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 | -------------------------------------------------------------------------------- /RWFileTransferDemoTests/RWFileTransferDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWFileTransferDemoTests.m 3 | // RWFileTransferDemoTests 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RWFileTransferDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation RWFileTransferDemoTests 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 | -------------------------------------------------------------------------------- /RWFileTransferDemoUITests/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 | -------------------------------------------------------------------------------- /RWFileTransferDemoUITests/RWFileTransferDemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RWFileTransferDemoUITests.m 3 | // RWFileTransferDemoUITests 4 | // 5 | // Created by RyanWong on 2018/1/17. 6 | // Copyright © 2018年 RyanWong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RWFileTransferDemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation RWFileTransferDemoUITests 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 | --------------------------------------------------------------------------------