iOS开发小技巧

很多时候,技巧这件事能让你事半功倍,在自己的工作开发中也尝试总结了一些经常使用到的小技巧。

1.程序设计思路

  • 分离重量级类,可以使用类别category的思路,比如拆解AppDelegate。
  • 防止强依赖第三方库,使用业务层封装业务逻辑。
    比如网路访问如果每个页面都使用AFN,假如有一天我们放弃使用AFN,或者AFN过时不更。。所以我们抽取一个网络访问业务类,当需求改变只需要更改网络访问业务类。
    其他类似的比如数据存储,照片浏览,弹框提示等。
    结论就是当我们遇到第三方甚至是常用系统方法的时候都可以进行业务类封装。
  • 数据决定样式,重复代码写父类
    比如一个不同Cell格式的设置页面,使用自定义的数据模型决定Cell样式
    MineCellGroupLayoutData:
@interface MineCellGroupLayoutData : NSObject
@property(nonatomic,strong) NSString * headerName;
@property(nonatomic,strong) NSString * footerName;
@property(nonatomic,strong) NSArray<MineCellLayoutData*> * layoutDataArr;
@end

MineCellLayoutData:

typedef void(^TaskBlock)(void);
@interface MineCellLayoutData : NSObject
@property(nonatomic,strong) NSString * leftImgName;
@property(nonatomic,strong) NSString * contentName;
@property(nonatomic,strong) NSString * rightName;
@property(nonatomic,copy) TaskBlock task;
@end

BaseFuncTableViewController(抽取一个实现TableView的父类):

@implementation BaseFuncTableViewController

-(void)addSubViews
{
    UITableView * tv=[[UITableView alloc]initWithFrame:CGRectMake(0, 0, SDScreenWidth, SDScreenHeight) style:UITableViewStyleGrouped];
    [tv registerNib:[UINib nibWithNibName:@"MineTableViewCell_func" bundle:nil] forCellReuseIdentifier:@"mineTableViewCell_func"];
    tv.delegate=self;
    tv.dataSource=self;
    tv.separatorStyle=UITableViewCellSeparatorStyleNone;
    tv.backgroundColor=SDColor(246, 246, 246);
    [self.view addSubview:tv];
    self.tableview=tv;
    
    self.needNoNetTips=YES;
    self.needNoTableViewDataTips=YES;
    self.baseTableview=self.tableview;

}

#pragma mark - UITableView代理

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return self.layoutData.count;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    MineCellGroupLayoutData * grouplayout=self.layoutData[section];
    return grouplayout.layoutDataArr.count;
}

-(UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    UIView * view=[[UIView alloc]initWithFrame:CGRectMake(0, 0, SDScreenWidth, 10)];
    view.backgroundColor=SDColor(246, 246, 246);
    return view;
}

-(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    UIView * view=[[UIView alloc]initWithFrame:CGRectMake(0, 0, SDScreenWidth, 0.1)];
    view.backgroundColor=SDColor(246, 246, 246);
    return view;
}

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 10.0;
}

-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
    return 0.1;
}


-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 50;
}

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MineCellGroupLayoutData * grouplayout=self.layoutData[indexPath.section];
    MineCellLayoutData * layout=grouplayout.layoutDataArr[indexPath.row];
    MineTableViewCell_func * cell=[MineTableViewCell_func cellForTableView:tableView andReuseId:@"mineTableViewCell_func"];
    cell.layoutData=layout;
    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    MineCellGroupLayoutData * grouplayout=self.layoutData[indexPath.section];
    MineCellLayoutData * layout=grouplayout.layoutDataArr[indexPath.row];
    if(layout.task)
    {
        layout.task();
    }
}


-(NSMutableArray *)layoutData
{
    if(_layoutData==nil)
    {
        _layoutData=[NSMutableArray array];
    }
    return _layoutData;
}

MineViewController_set(仅仅实现不同数据源的子类):

@implementation MineViewController_set

-(void)addSubViews
{
    [self setData];
    [super addSubViews];
}

-(void)setNavigationBar
{
    self.title=@"设置";
}

-(void)setData
{
    MineCellGroupLayoutData * group1=[[MineCellGroupLayoutData alloc]init];
    
    MineCellLayoutData * data1=[[MineCellLayoutData alloc]init];
    data1.contentName=@"账号管理";
    
    MineCellLayoutData * data2=[[MineCellLayoutData alloc]init];
    data2.contentName=@"账号安全";
    
    MineCellLayoutData * data3=[[MineCellLayoutData alloc]init];
    data3.contentName=@"消息提醒";
    
    MineCellLayoutData * data4=[[MineCellLayoutData alloc]init];
    data4.contentName=@"清除缓存";
    data4.task = ^{
        [self clearFile];
    };
    float size=[JSXSystemInfoTool getSystemCacheSize];
    data4.rightName=[NSString stringWithFormat:@"%.2fM",size];
    
    group1.layoutDataArr=@[data1,data2,data3,data4];
    
    NSString * app_Version=[JSXSystemInfoTool getSystemVersionInfo];
    MineCellLayoutData * data5=[[MineCellLayoutData alloc]init];
    data5.contentName=@"版本号";
    data5.rightName=app_Version;
    
    MineCellGroupLayoutData * group2=[[MineCellGroupLayoutData alloc]init];
    group2.layoutDataArr=@[data5];
    
    [self.layoutData addObject:group1];
    [self.layoutData addObject:group2];
}

2.iOS分类

默认分类不能添加属性(因为不会生成对应_属性名称,所以不会自带setter和getter方法),但是可以通过runtime动态绑定来实现

3.按钮动态绑定属性传递数据

[descBtn addTarget:self action:@selector(clickdescBtn:)forControlEvents:UIControlEventTouchUpInside];
objc_setAssociatedObject(descBtn,"data",sd,OBJC_ASSOCIATION_RETAIN_NONATOMIC);

//获取数据
-(void)clickdescBtn:(UIButton*)button
{
id data = objc_getAssociatedObject(button,"data");
}

还有一种办法就是继承UIButton

4.高度自适应

首先来看单行文本的问题:对于单行文本来说,计算CGSize就比较简单了,这里直接上代码了,如下:

NSString *content = @"欢迎来到北京";
CGSize size =[contentsizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]}];

其中14是字体大小,你可以随意指定
最后来看多行文本的显示:
首先UILabel的numberOfLines设置为0,其次通过- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context方法来计算CGSize,具体代码如下:

UILabel *titleLabel = [UILabel new];
titleLabel.font = [UIFont systemFontOfSize:14];
NSString *titleContent = @"亲,欢迎您通过以下方式与我们的营销顾问取得联系,交流您再营销推广工作中遇到的问题,营销顾问将免费为您提供咨询服务。";
titleLabel.text = titleContent;
titleLabel.numberOfLines = 0;//多行显示,计算高度
titleLabel.textColor = [UIColor lightGrayColor];
CGSize titleSize = [titleContent boundingRectWithSize:CGSizeMake(kScreen_Width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]} context:nil].size;
titleLabel.size = titleSize;
titleLabel.x = 0;
titleLabel.y = 0;
[self.view addSubview:titleLabel];

5.GitHub正确使用方式,搜索关键字,比如HUD,QRCode。

6.通过类名创建类 UIViewController* vc = [[NSClassFromString(@"vc")alloc]init];

7.Base类的作用就是在处理子类统一操作,比如导航控制器,在push的时候隐藏tabbar并添加返回按钮。

- (void)pushViewController:(UIViewController*)viewController animated:(BOOL)animated
{
if(self.viewControllers.count>0) {//这时push进来的控制器viewController,不是第一个子控制器(不是根控制器)
viewController.hidesBottomBarWhenPushed=YES;
viewController.navigationItem.leftBarButtonItem= [UIBarButtonItemitemWithTarget:selfaction:@selector(back)image:nilhighImage:niltitle:@"返回"];
}
[superpushViewController:viewControlleranimated:animated];
}

8.一个页面有N个控制器,使用方法addChildViewController。比如映客直播+蒙版互动页面。

VC2* vc2 = [[VC2alloc]init];
[self addChildViewController:vc2];
vc2.view.frame=self.view.bounds;
[self.viewaddSubview:vc2.view];

9.直播

拉流框架:https://github.com/Bilibili/ijkplayer
推流框架:https://github.com/LaiFengiOS/LFLiveKit

10.SDImage提供SDWebImageManager来下载网络图片。

广告业面展示方式:1.展示本地图片 2下载图片(供下次展示)

11.类似固定cell的情况,可以创建一个datamodel进行动态赋值和跳转。datamodel包含cell数据,跳转控制器名称以及xib名称等。

12.view添加手势会导致和上面tableview的didSelectRowAtIndexPath方法冲突,所以需要判断,如果点击tableview或者cell,则让手势失效。

-(BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer shouldReceiveTouch:(UITouch*)touch
{
//如果当前是tableView
if([NSStringFromClass([touch.viewclass])isEqualToString:@"UITableViewCellContentView"] ||[NSStringFromClass([touch.viewclass])isEqualToString:@"UITableView"]) {
//做自己想做的事
returnNO;
}
returnYES;
}

13.Xcode技巧

在方法或者属性上点击ESC->查看函数返回值或者属性类型。
点击Alt(Option)->查看函数文档信息。
commond+option+/->文档注释(函数提示信息)
双视图页面->preview->查看各个尺寸模拟器UI效果

14.NSURL中文路径需要做UTF-8编码。

 NSString * str=[NSString stringWithFormat:@"file://中文"];
        //iOS9.0以前
        //NSString *urlStr = [str stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
NSString * urlStr = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
NSURL * url=[NSURL URLWithString:urlStr];
NSLog(@"url%@",url);

15.状态栏修改

info.plist:
View controller-based status bar appearance=NO;
不允许控制器啊管理状态栏状态,需要全局设置

UIApplication *app=[UIApplication sharedApplication];
    app.statusBarStyle=UIStatusBarStyleDefault;
    app.statusBarHidden=NO;

View controller-based status bar appearance=YES;
允许控制器啊管理状态栏状态,每个控制器状态栏可以不同

-(UIStatusBarStyle)preferredStatusBarStyle
{
    return UIStatusBarStyleLightContent;
}

-(BOOL)prefersStatusBarHidden
{
    return YES;
}

16.文件下载上传的注意点

1.大文件下载不能使用内存缓存数据,而必须使用文件增量存储到外存。
增量缓存涉及的类NSFileHandle(文件句柄)或者NSOutputStream(输出流)
NSFileHandle

    //指定文件路径
    NSFileHandle * handle=[NSFileHandle fileHandleForWritingAtPath:@""];
    //增量写入数据
    [handle writeData:@""];
    //断点下载时,句柄移到文件末尾开始写入(默认指向文件开头)
    [handle seekToEndOfFile];
    //下载完成后关闭文件句柄
    [handle closeFile];

NSOutputStream

    //指定文件路径
    NSOutputStream * os=[NSOutputStream outputStreamWithURL:@"" append:YES];
    //打开输入流
    [os open];
    //下载完毕后关闭输入流
    [os close];
    

2.断点下载+离线下载
核心点是HTTP请求头中Range参数,可以指定从文件下载的区间,单位是字节bytes。
离线下载就是根据文件中已经下载的文件大小和文件总大小找到指定Range。
3.具体方式
NSURLSession和AFN
https://www.jianshu.com/p/01390c7a4957
使用NSURLSession可以使用dataTask或者downLoadTask两种任务。
前者需要使用NSFileHandle或者NSOutputStream手动写入文件,通过代理方法监听下载进度;
后者把下载文件自动写入tmp文件夹(如果需要,通过剪切粘贴方式改变存放路径),使用block方式无法监听到进度信息,也需要代理方式进行监听;取消下载的时候提供一个resumeData数据,保存记录取消下载时的文件位置信息,在恢复下载的时候可以从该resumeData记录的位置恢复,但是如果实现离线下载,还是需要利用HTTP请求头中Range和NSFileHandle/NSOutputStream读取到本地文件信息。
使用AFN更加简洁,只需要指定下载路径就行了。

+(void)downLoadofAFN:(NSString *)url params:(NSDictionary *)params location:(NSURL *)location andProgress:(void (^)(float))progress success:(void (^)(id))success failure:(void (^)(NSError *))failure
{
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    
    NSURL *URL = [NSURL URLWithString:url];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    
    NSURLSessionDownloadTask *downloadTask =[manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
        if(progress)
        {
            NSLog(@"下载进度:%lld", downloadProgress.completedUnitCount/downloadProgress.totalUnitCount);
            progress(downloadProgress.completedUnitCount/downloadProgress.totalUnitCount);
        }
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        return location;
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        if (error) {
            failure(error);
        }else
        {
            if(success)
            {
               NSLog(@"File downloaded to: %@", filePath);
               success(response);
            }
        }
    }];
    [downloadTask resume];
    
}

4.文件上传,大文件需要进行压缩解压缩处理
https://github.com/ZipArchive/ZipArchive
使用NSURLSession,使用POST请求,而且需要自己拼接请求头信息,指定Content-Type(Mime-Type)-(可以使用通用application/octet-stream,也可以通过响应头获取)。
使用AFN,

        NSString * key=imgDict.allKeys[0];
        UIImage * img=imgDict[key];
        NSData *imageData =UIImageJPEGRepresentation(img,comress);
        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
        manager.requestSerializer = [AFHTTPRequestSerializer serializer];
        manager.responseSerializer = [AFHTTPResponseSerializer serializer];
        [manager POST:Interface_UploadHeadImg parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
            [formData appendPartWithFileData:imageData name:key fileName:[NSString stringWithFormat:@"%@.jpeg",key] mimeType:@"application/octet-stream"];
        } progress:^(NSProgress * _Nonnull uploadProgress) {
        } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:nil];
            SDLog(@"成功:%@",dictionary);
            if(success) {
                success(dictionary);
            }
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            SDLog(@"失败:%@",error);
            if(failure) {
                failure(error);
            }
        }];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容