iOS开发-仿微信图片分享界面实现
分享功能目前几乎已成为很多app的标配了,其中微信,微博等app的图片分享界面设计的很棒,不仅能够展示缩略图,还可以预览删除。最近我在做一款社交分享app,其中就要实现图文分享功能,于是试着自行实现仿微信分享风格的功能。
核心思想:
主要是使用UICollectionView来动态加载分享图片内容,配合预览页面,实现动态添加和预览删除图片效果。
实现效果:
核心代码如下:
分享界面:
// // PostTableViewController.h // NineShare // // Created by 张昌伟 on 15/1/26. // Copyright (c) 2015年 9Studio. All rights reserved. // #import <UIKit/UIKit.h> #import "UMSocial.h" #import "YSYPreviewViewController.h" @interface PostTableViewController : UITableViewController<UITextViewDelegate,UICollectionViewDataSource,UICollectionViewDelegate,UIActionSheetDelegate,UIImagePickerControllerDelegate,UMSocialUIDelegate,UINavigationControllerDelegate> @property (weak, nonatomic ) IBOutlet UICollectionView *photosCollectionView; @property (weak, nonatomic ) IBOutlet UISwitch *WeiboSwitch; @property (weak, nonatomic ) IBOutlet UISwitch *RenrenSwitch; - ( IBAction )DoubanSwitched:( id )sender; - ( IBAction )RenrenSwitched:( id )sender; - ( IBAction )WeiboSwitched:( id )sender; +( void ) deleteSelectedImage:( NSInteger ) index; +( void ) deleteSelectedImageWithImage:(UIImage*)image; @end |
实现文件
// // PostTableViewController.m // NineShare // // Created by 张昌伟 on 15/1/26. // Copyright (c) 2015年 9Studio. All rights reserved. // #import "PostTableViewController.h" #import "NineShareService.h" static NSMutableArray *currentImages; @interface PostTableViewController () @property (weak, nonatomic ) IBOutlet UITextView *shareContent; - ( IBAction )postStatus:( id )sender; - ( IBAction )cancelPost:( id )sender; -( void ) loadSNSStatus; @property (weak, nonatomic ) IBOutlet UISwitch *DoubanSwitch; @property (weak, nonatomic ) IBOutlet UITextView *backgroundTextView; @property NSMutableArray *snsArray; //@property NSMutableArray *photos; @property NineShareService *dataContext; @property NSMutableDictionary *tempDict; -( void ) openCamera; -( void ) openLibary; @end @implementation PostTableViewController - ( void )viewDidLoad { [ super viewDidLoad]; if (currentImages == nil ) { currentImages=[[ NSMutableArray alloc] init]; } // Uncomment the following line to preserve selection between presentations. // self.clearsSelectionOnViewWillAppear = NO; // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem; _dataContext=[NineShareService getInstance]; [ self loadSNSStatus]; } -( void )viewWillAppear:( BOOL )animated{ [_photosCollectionView reloadData]; } - ( void )didReceiveMemoryWarning { [ super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } -( void ) loadSNSStatus{ _snsArray=[ NSMutableArray arrayWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:@ "sns" ofType:@ "plist" ]]; if (_snsArray.count>0) { [_WeiboSwitch setOn:[_snsArray[0] boolValue] animated: YES ]; [_RenrenSwitch setOn:[_snsArray[1] boolValue] animated: YES ]; [_DoubanSwitch setOn:[_snsArray[2] boolValue] animated: YES ]; } } -( BOOL )textView:(UITextView *)textView shouldChangeTextInRange:( NSRange )range replacementText:( NSString *)text{ if (![text isEqualToString:@ "" ]) { [_backgroundTextView setHidden: YES ]; } if ([text isEqualToString:@ "" ]&&range.length==1&&range.location==0){ [_backgroundTextView setHidden: NO ]; } if ([text isEqualToString:@ "\n" ]) { [textView resignFirstResponder]; return NO ; } return YES ; } -( void )textViewDidBeginEditing:(UITextView *)textView { CGRect frame = textView.frame; int offset = frame.origin.y + 32 - ( self .view.frame.size.height - 216.0); //键盘高度216 NSTimeInterval animationDuration = 0.30f; [UIView beginAnimations:@ "ResizeForKeyboard" context: nil ]; [UIView setAnimationDuration:animationDuration]; //将视图的Y坐标向上移动offset个单位,以使下面腾出地方用于软键盘的显示 if (offset > 0) self .view.frame = CGRectMake(0.0f, -offset, self .view.frame.size.width, self .view.frame.size.height); [UIView commitAnimations]; } -( void )textViewDidEndEditing:(UITextView *)textView{ self .view.frame =CGRectMake(0, 0, self .view.frame.size.width, self .view.frame.size.height); } -( void )collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:( NSIndexPath *)indexPath{ if (indexPath.row==currentImages.count) { UIActionSheet *action=[[UIActionSheet alloc] initWithTitle:@ "选取照片" delegate: self cancelButtonTitle:@ "取消" destructiveButtonTitle: nil otherButtonTitles:@ "从摄像头选取" , @ "从图片库选择" , nil ]; [action showInView: self .view]; } else { [YSYPreviewViewController setPreviewImage:currentImages[indexPath.row]]; [ self .navigationController pushViewController:[[UIStoryboard storyboardWithName:@ "Main" bundle:[ NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@ "PreviewVC" ] animated: YES ]; } } -( NSInteger )collectionView:(UICollectionView *)collectionView numberOfItemsInSection:( NSInteger )section{ return currentImages.count==0?1:currentImages.count+1; } -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:( NSIndexPath *)indexPath{ UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:@ "collectionCell" forIndexPath:indexPath]; UIImageView *imageView=[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)]; if (currentImages.count==0||indexPath.row==currentImages.count) { imageView.image=[UIImage imageNamed:@ "Add" ]; } else { while ([cell.contentView.subviews lastObject] != nil ) { [(UIView*)[cell.contentView.subviews lastObject] removeFromSuperview]; } imageView.image=currentImages[indexPath.row]; } imageView.contentMode=UIViewContentModeScaleAspectFill; [cell.contentView addSubview:imageView]; return cell; } -( void )saveSNSToFile{ NSString *destPath=[ NSSearchPathForDirectoriesInDomains ( NSDocumentDirectory , NSUserDomainMask , YES ) lastObject]; if (![[ NSFileManager defaultManager] fileExistsAtPath:destPath]) { NSString *path=[[ NSBundle mainBundle] pathForResource:@ "sns" ofType:@ "plist" ]; [[ NSFileManager defaultManager] copyItemAtPath:path toPath:destPath error: nil ]; } if (_snsArray== nil ) _snsArray=[[ NSMutableArray alloc] init]; [_snsArray removeAllObjects]; [_snsArray addObject:_WeiboSwitch.isOn?@ "YES" :@ "NO" ]; [_snsArray addObject:_RenrenSwitch.isOn?@ "YES" :@ "NO" ]; [_snsArray addObject:_DoubanSwitch.isOn?@ "YES" :@ "NO" ]; if (_snsArray.count>0) { [_snsArray writeToFile:destPath atomically: YES ]; } } - ( IBAction )postStatus:( id )sender { if (_WeiboSwitch.isOn) [[UMSocialDataService defaultDataService] postSNSWithTypes:@[UMShareToSina] content:_shareContent.text.length>0?_shareContent.text: @ "9Share for ios test message" image:currentImages.count==0? nil :currentImages[0] location: nil urlResource: nil presentedController: self completion:^(UMSocialResponseEntity *response){ if (response.responseCode == UMSResponseCodeSuccess) { NSLog (@ "分享成功!" ); if (!(_RenrenSwitch.isOn||_DoubanSwitch.isOn)) { [ self saveSNSToFile]; [ self dismissViewControllerAnimated: YES completion: nil ]; } } }]; if (_RenrenSwitch.isOn) [[UMSocialDataService defaultDataService] postSNSWithTypes:@[UMShareToRenren] content:_shareContent.text.length>0?_shareContent.text: @ "9Share for ios test message" image:currentImages.count==0? nil :currentImages[0] location: nil urlResource: nil presentedController: self completion:^(UMSocialResponseEntity *response){ if (response.responseCode == UMSResponseCodeSuccess) { NSLog (@ "分享成功!" ); if (!_DoubanSwitch.isOn) { [ self saveSNSToFile]; [ self dismissViewControllerAnimated: YES completion: nil ]; } } }]; if (_DoubanSwitch.isOn) [[UMSocialDataService defaultDataService] postSNSWithTypes:@[UMShareToDouban] content:_shareContent.text.length>0?_shareContent.text: @ "9Share for ios test message" image:currentImages.count==0? nil :currentImages[0] location: nil urlResource: nil presentedController: self completion:^(UMSocialResponseEntity *response){ if (response.responseCode == UMSResponseCodeSuccess) { NSLog (@ "分享成功!" ); [ self saveSNSToFile]; [ self dismissViewControllerAnimated: YES completion: nil ]; } }]; } -( void )imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:( NSDictionary *)info{ [picker dismissViewControllerAnimated: YES completion: nil ]; UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage]; NSData *tempData=UIImageJPEGRepresentation(image, 0.5f); image=[UIImage imageWithData:tempData]; if (currentImages == nil ) { currentImages=[[ NSMutableArray alloc] init]; } [currentImages addObject:image]; [_photosCollectionView reloadData]; // [self saveImage:image withName:@""] } -( void )imagePickerControllerDidCancel:(UIImagePickerController *)picker{ [picker dismissViewControllerAnimated: YES completion: nil ]; } - ( IBAction )cancelPost:( id )sender { [ self dismissViewControllerAnimated: YES completion: nil ]; } -( void )actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:( NSInteger )buttonIndex{ switch (buttonIndex) { case 0: [ self openCamera]; break ; case 1: [ self openLibary]; break ; default : break ; } } -( void )openCamera{ //UIImagePickerControllerSourceType *type=UIImagePickerControllerSourceTypeCamera; if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { UIImagePickerController *picker=[[UIImagePickerController alloc] init]; picker.delegate= self ; picker.sourceType=UIImagePickerControllerSourceTypeCamera; picker.allowsEditing= YES ; [ self presentViewController:picker animated: YES completion: nil ]; } } -( void )openLibary{ if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { UIImagePickerController *picker=[[UIImagePickerController alloc] init]; picker.delegate= self ; picker.sourceType=UIImagePickerControllerSourceTypePhotoLibrary; picker.allowsEditing= YES ; [ self presentViewController:picker animated: YES completion: nil ]; } } -( void ) saveImage:(UIImage *)image withName:( NSString *)name { NSData *imageData=UIImageJPEGRepresentation(image, 0.5); NSString *path=[ NSTemporaryDirectory () stringByAppendingPathComponent:name]; [imageData writeToFile:path atomically: YES ]; } - ( IBAction )DoubanSwitched:( id )sender { if (_DoubanSwitch.isOn){ if (![UMSocialAccountManager isOauthAndTokenNotExpired:UMShareToDouban]) { //进入授权页面 [UMSocialSnsPlatformManager getSocialPlatformWithName:UMShareToDouban].loginClickHandler( self ,[UMSocialControllerService defaultControllerService], YES ,^(UMSocialResponseEntity *response){ if (response.responseCode == UMSResponseCodeSuccess) { //获取微博用户名、uid、token等 UMSocialAccountEntity *snsAccount = [[UMSocialAccountManager socialAccountDictionary] valueForKey:UMShareToDouban]; NSLog (@ "username is %@, uid is %@, token is %@" ,snsAccount.userName,snsAccount.usid,snsAccount.accessToken); //进入你的分享内容编辑页面 UMSocialAccountEntity *doubanAccount = [[UMSocialAccountEntity alloc] initWithPlatformName:UMShareToDouban]; doubanAccount.usid = snsAccount.usid; doubanAccount.accessToken = snsAccount.accessToken; // weiboAccount.openId = @"tencent weibo openId"; //腾讯微博账户必需设置openId //同步用户信息 [UMSocialAccountManager postSnsAccount:doubanAccount completion:^(UMSocialResponseEntity *response){ if (response.responseCode == UMSResponseCodeSuccess) { //在本地缓存设置得到的账户信息 [UMSocialAccountManager setSnsAccount:doubanAccount]; //进入你自定义的分享内容编辑页面或者使用我们的内容编辑页面 }}]; } else { [_DoubanSwitch setOn: NO animated: YES ]; } }); } } } - ( IBAction )RenrenSwitched:( id )sender { if (_DoubanSwitch.isOn) { if (![UMSocialAccountManager isOauthAndTokenNotExpired:UMShareToRenren]) { //进入授权页面 [UMSocialSnsPlatformManager getSocialPlatformWithName:UMShareToRenren].loginClickHandler( self ,[UMSocialControllerService defaultControllerService], YES ,^(UMSocialResponseEntity *response){ if (response.responseCode == UMSResponseCodeSuccess) { //获取微博用户名、uid、token等 UMSocialAccountEntity *snsAccount = [[UMSocialAccountManager socialAccountDictionary] valueForKey:UMShareToRenren]; NSLog (@ "username is %@, uid is %@, token is %@" ,snsAccount.userName,snsAccount.usid,snsAccount.accessToken); //进入你的分享内容编辑页面 UMSocialAccountEntity *renrenAccount = [[UMSocialAccountEntity alloc] initWithPlatformName:UMShareToRenren]; renrenAccount.usid = snsAccount.usid; renrenAccount.accessToken = snsAccount.accessToken; // weiboAccount.openId = @"tencent weibo openId"; //腾讯微博账户必需设置openId //同步用户信息 [UMSocialAccountManager postSnsAccount:renrenAccount completion:^(UMSocialResponseEntity *response){ if (response.responseCode == UMSResponseCodeSuccess) { //在本地缓存设置得到的账户信息 [UMSocialAccountManager setSnsAccount:renrenAccount]; //进入你自定义的分享内容编辑页面或者使用我们的内容编辑页面 }}]; } else { [_RenrenSwitch setOn: NO animated: YES ]; } }); } } } - ( IBAction )WeiboSwitched:( id )sender { if (_WeiboSwitch.isOn) { if (![UMSocialAccountManager isOauthAndTokenNotExpired:UMShareToSina]) { [UMSocialSnsPlatformManager getSocialPlatformWithName:UMShareToSina].loginClickHandler( self ,[UMSocialControllerService defaultControllerService], YES ,^(UMSocialResponseEntity *response){ if (response.responseCode==UMSResponseCodeSuccess){ UMSocialAccountEntity *snsAccount=[[UMSocialAccountManager socialAccountDictionary] valueForKey:UMShareToSina]; UMSocialAccountEntity *sinaAccount=[[UMSocialAccountEntity alloc] initWithPlatformName:UMShareToSina]; //缓存到本地 sinaAccount.usid = snsAccount.usid; sinaAccount.accessToken = snsAccount.accessToken; // weiboAccount.openId = @"tencent weibo openId"; //腾讯微博账户必需设置openId //同步用户信息 [UMSocialAccountManager postSnsAccount:sinaAccount completion:^(UMSocialResponseEntity *response){ if (response.responseCode == UMSResponseCodeSuccess) { //在本地缓存设置得到的账户信息 [UMSocialAccountManager setSnsAccount:sinaAccount]; //进入你自定义的分享内容编辑页面或者使用我们的内容编辑页面 }}]; } else { [_WeiboSwitch setOn: NO animated: YES ]; } }); } } } +( void )deleteSelectedImage:( NSInteger )index { if (currentImages!= nil ) [currentImages removeObjectAtIndex:index]; } +( void )deleteSelectedImageWithImage:(UIImage *)image{ if (currentImages!= nil ) [currentImages removeObject:image]; } @end |
预览界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // // YSYPreviewViewController.h // NineShare // // Created by ZhangChangwei on 15/2/1. // Copyright (c) 2015年 9Studio. All rights reserved. // #import <UIKit/UIKit.h> #import "PostTableViewController.h" @interface YSYPreviewViewController : UIViewController<UIActionSheetDelegate> +( void ) setPreviewImage:(UIImage *)image; @end |
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | // // YSYPreviewViewController.m // NineShare // // Created by ZhangChangwei on 15/2/1. // Copyright (c) 2015年 9Studio. All rights reserved. // #import "YSYPreviewViewController.h" static UIImage *currentImage; @interface YSYPreviewViewController () - ( IBAction )deleteSelectedImage:( id )sender; @property (weak, nonatomic ) IBOutlet UIImageView *previewImageView; @end @implementation YSYPreviewViewController - ( void )viewDidLoad { [ super viewDidLoad]; // Do any additional setup after loading the view. _previewImageView.image=currentImage; } - ( void )didReceiveMemoryWarning { [ super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ - ( IBAction )deleteSelectedImage:( id )sender { UIActionSheet *action=[[UIActionSheet alloc] initWithTitle:@ "要删除这张照片吗?" delegate: self cancelButtonTitle:@ "取消" destructiveButtonTitle:@ "删除" otherButtonTitles: nil ]; [action showInView: self .view]; } -( void )actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:( NSInteger )buttonIndex{ if (buttonIndex==actionSheet.cancelButtonIndex) { return ; } else { [PostTableViewController deleteSelectedImageWithImage:currentImage]; [ self .navigationController popToRootViewControllerAnimated: YES ]; } } +( void )setPreviewImage:(UIImage *)image{ currentImage=image; } @end |
到这里就可以实现完整的带预览删除的图文分享功能了^_^
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?