美文网首页
iOS开发小技巧

iOS开发小技巧

作者: 宙斯YY | 来源:发表于2018-05-09 17:48 被阅读159次

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

    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);
                }
            }];
    

    相关文章

      网友评论

          本文标题:iOS开发小技巧

          本文链接:https://www.haomeiwen.com/subject/ysjorftx.html