• 【iOS】知乎日报前三周总结


    这几天一直在进行知乎日报的仿写,仿写过程中积累了许多实用的开发经验,并对MVC有了更深的了解,特撰此篇作以总结



    第一周

    网络请求封装在一个单例类Manager中

    由于知乎日报所请求的API较多,若将网络请求三板斧直接写在Controller中会代码十分冗杂,干脆直接将AFNetWorking和JSONModel封装到一个全局的Manager单例类中,在Manager类中进行网络请求和数据解析,不同的API写成不同的方法,具体实现看这篇文章将网络请求封装在一个单例类Manager中(AFNetworking、JSONModel)

    typedef void(^LatestStoriesBlock)(LatestStoriesModel* latestStoriesModel);
    typedef void(^BeforeStoriesModelBlock)(StoriesModel* beforeStoriesModel);
    typedef void(^StoriesContentBlock)(StoriesContentModel* storiesContentModel);
    typedef void(^StoriesExtraContentBlock)(StoriesExtraContentModel* storiesExtraContentModel);
    typedef void(^ErrorBlock)(NSError* error);
    
    @interface Manager : NSObject
    
    //单例实例
    + (instancetype)sharedManager;
    
    //请求最新消息
    - (void)requestTopStoriesData: (LatestStoriesBlock)success failure: (ErrorBlock)failure;
    
    //缓存网络图片
    + (void)setImage: (id)imageView WithString: (NSString *)string;
    
    //请求指定日期的消息内容
    - (void)requestBeforeDate: (NSString *)date beforeStoriesData: (BeforeStoriesModelBlock)success failure: (ErrorBlock)failure;
    
    //加载Web网页在WebView中
    - (void)setWebView: (WKWebView *)webView WithString: (NSString *)string;
    
    //请求指定网页的内容(主要为了获取share_url)
    - (void)requestWebContentWithID: (NSString *)string StoriesContentData: (StoriesContentBlock)success failure: (ErrorBlock)failure;
    
    //请求制定网页的额外内容
    - (void)requestExtraContentWithID: (NSString *)string StoriesExtraContentData: (StoriesExtraContentBlock)success failure: (ErrorBlock)failure;
    
    @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

    解析下来的数据用Block传值(【iOS】属性传值、代理传值(委托)、通知传值、KVO传值、Block传值、单例传值)的方式在Controller中接收

    SDWebImage库的简单使用

    使用WebImage库主要是为了缓存网络图片并加载在UIImageView上,只需在loadRequest:方法中传入指定的NSURL

    + (void)setImage:(UIImageView *)imageView WithString:(NSString *)string {
        string = [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
        NSURL* url = [NSURL URLWithString:string];
        
        [imageView sd_setImageWithURL: url placeholderImage: [UIImage imageNamed: @"placeholder.png"]];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    运用时间戳处理当前时间

    时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

    处理方法单独写在一个工具类中方便管理

    @interface DateModel : NSObject
    
    + (NSTimeInterval)getTimestampWithTimeString: (NSString *)timeString;
    + (NSString *)getMonthWithTimeString: (NSString *)timeString;
    + (NSString *)getDayWithTimeString: (NSString *)timeString;
    + (NSString *)getDateWithTimeString: (NSString *)timeString;
    + (NSString *)getBeforeDateWithTimeString: (NSString *)timeString;
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    获取时间戳方法

    //传入时间格式:20230917
    + (NSTimeInterval)getTimestampWithTimeString: (NSString *)timeString {
        NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateFormat: @"YYYYMMdd"];
        
        NSDate* date = [dateFormatter dateFromString: timeString];
        
        NSTimeInterval timeStamp = [date timeIntervalSince1970];
        return timeStamp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过时间戳处理指定时间

        NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
    
        //YYYY获取年份 MM获取月份 dd获取日份 
        //当然也可以混用,如:@"MM月dd日",这样就会得到一个指定格式的时间字符串
        [dateFormatter setDateFormat: @"MM"];
        
        NSTimeInterval timeStamp = [self getTimestampWithTimeString: timeString];
        NSDate* date = [NSDate dateWithTimeIntervalSince1970: timeStamp];
        
        NSString* string = [dateFormatter stringFromDate: date];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    自定义NavigationBar

    请添加图片描述

    在push页面过程中,使用系统的NavigationBar会发现UI是逐渐消失的,而知乎日报App是第二个页面直接覆盖过去的,想不到什么实现这种效果的巧妙方法,这里我直接在NavigationBar的位置直接粘上一个UIView,并在当前控制器隐藏NavigationBar

    - (void)viewWillAppear:(BOOL)animated {
        [self.navigationController setNavigationBarHidden: YES animated: YES];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self.navigationController setNavigationBarHidden: NO animated: YES];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这两个方法涉及UIViewController的生命周期【iOS】ViewController生命周期


    第二周

    在UITableView的section之间的headerView上画UI

    请添加图片描述

    tableView提供了这样一个协议方法自定义section

    - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
        if (section > 1) {
            UIView* view = [[UIView alloc] init];
            
            UILabel* dateLabel = [[UILabel alloc] init];
            dateLabel.textColor = [UIColor grayColor];
            dateLabel.font = [UIFont boldSystemFontOfSize: 15];
            dateLabel.text = [DateModel getDateWithTimeString: self.beforeDateArray[section - 1]];
            [view addSubview: dateLabel];
            
            UIView* grayView = [[UIView alloc] init];
            grayView.backgroundColor = [UIColor colorWithRed: 231.0 / 255 green: 231.0 / 255 blue: 231.0 / 255 alpha: 1];
            [view addSubview: grayView];
            
            [dateLabel makeConstraints:^(MASConstraintMaker *make) {
                make.left.equalTo(view.left).offset(MINIGAP);
                make.centerY.equalTo(view.centerY);
            }];
            
            [grayView makeConstraints:^(MASConstraintMaker *make) {
                make.height.equalTo(1);
                make.left.equalTo(dateLabel.right).offset(MINIGAP);
                make.right.equalTo(view.right);
                make.centerY.equalTo(view.centerY);
            }];
            
            return view;
        } else {
            return nil;
        }
    }
    
    • 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

    但section之间的距离跟返回view的高度是没有关系的,要想消除分区之间的留白,给以下属性赋值即可

     self.tableView.sectionHeaderTopPadding = 0;
    
    • 1

    WKWebView的使用

    只需传入NSRequest即可在webView上加载web网页

    - (void)setWebView:(WKWebView *)webView WithString:(NSString *)string {
        //NSLog(@"%@", string);
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: string]];
        [webView loadRequest: request];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    WKWebView网页加载不出来

    大概查了一下

    iOS9引入了新特性:App Transport Security (ATS),新特性要求App内访问的网络必须使用HTTPS协议。
    但是现在公司的项目使用的是HTTP协议,使用私有加密方式保证数据安全。现在也不能马上改成HTTPS协议传输。

    出现加载不出来本质原因是,如果没在Info.plist文件中添加App Transport Security Settings并将Allow Arbitrary Loads设置为YES的话,

    加载https链接,肯定是加载不出来的,第一次加载不出来,产生了缓存,第二次再去加载也加载不出来了,可以尝试一下就知道了
    请添加图片描述

    线程问题

    网络请求是在Controller中调用的,请求下来的数据会赋值给View的各种属性,但请求是需要时间的,如果在viewDidLoad里面先初始化view,那么还没等数据请求下来,UI就已经布局,那么只会显示空白的UI控件,未解决这一问题,用到了GCD的一个方法:

            dispatch_async(dispatch_get_main_queue(), ^{
                
                [self sendViewStories: latestStoriesModel];
                [self createTopImages];
                [self setViewAndModel];
                [self requestBeforeStoriesWithDate: self.latestStoriesModel.date];
                
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第三周

    请添加图片描述

    attempting to add unsupported attribute: (null)错误

    使用masonry,设置约束时,约束冲突或约束不全

    解决方法就是补充必要的约束或者调整调用顺序

    FMDB数据库的简单使用

    FMDB库将C语言风格的MySqlite数据库封装成了OC风格的方法,而且操作比C语言文件操作更容易

    以点赞集合(存储相应网页的ID)为例,实现了数据库的增删改查:

    #pragma mark 点赞操作
    - (void)createStoriesLikeSet {
        NSString* doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString* fileName = [doc stringByAppendingPathComponent: @"likeDatabase.sqlite"];
        NSLog(@"%@", fileName);
        self.likeDatabase = [FMDatabase databaseWithPath: fileName];
        
        if ([self.likeDatabase open]) {
            BOOL result = [self.likeDatabase executeUpdate: @"CREATE TABLE IF NOT EXISTS likeDatabase (idLabel text NOT NULL)"];
            if (result) {
                FMResultSet* resultSet = [self.likeDatabase executeQuery: @"SELECT * FROM likeDatabase"];
                while([resultSet next]) {
                    [self.storiesLikeSet addObject:[resultSet stringForColumn: @"idLabel"]];
                }
                NSLog(@"create table succeed");
            } else {
                NSLog(@"fail to open database");
            }
            [self.likeDatabase close];
        }
    }
    
    - (void)saveStoriesLikeSet {
        if ([self.likeDatabase open]) {
            for (NSString* ID in self.storiesLikeSet) {
                FMResultSet* resultSet = [self.likeDatabase executeQuery: @"SELECT * FROM likeDatabase WHERE idLabel = ?", ID];
                if (![resultSet next]) {
                    BOOL result = [self.likeDatabase executeUpdate: @"INSERT INTO likeDatabase (idLabel) VALUES (?)", ID];
                    if (result) {
                        NSLog(@"insert table succeed");
                    } else {
                        NSLog(@"insert table error");
                    }
                }
            }
            [self.likeDatabase close];
        }
    }
    
    - (void)deleteLikeSetWithID:(NSString*)ID {
        if ([self.likeDatabase open]) {
            BOOL result = [self.likeDatabase executeUpdate: @"delete from likeDatabase WHERE idLabel = ?", ID];
            if (result) {
                NSLog(@"delete table succeed");
            } else {
                NSLog(@"delete table error");
            }
            [self.likeDatabase close];
        }
    }
    
    • 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

    WebView界面无限右滑

    向右加载新一天的消息时,主页的tableViewCell也要更新,这里使用了通知中心进行消息传递

        if (self.scrollView.contentOffset.x == Screen_WIDTH * self.numberOfStories) {
            [[NSNotificationCenter defaultCenter] postNotificationName: @"upDateRight" object: nil];
            return;
        }
    
    • 1
    • 2
    • 3
    • 4

    滑到了新增加的画布时,先通知首页更新一天数据:

    //更新首页
    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(upDateRight) name: @"upDateRight" object: nil];
    
    - (void) upDateRight {
        StoriesModel* storiesModel = [self.beforeStoriesModelArray lastObject];
        NSString* beforeDate = storiesModel.date;
        [self requestBeforeStoriesWithDate: beforeDate];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后将更新后的数据传回WebView相关页面,加载新的Web

    - (void)requestBeforeStoriesWithDate: (NSString *)date {
        [self.manager requestBeforeDate: date beforeStoriesData:^(StoriesModel * _Nonnull beforeStoriesModel) {
                if (!self.beforeStoriesModelArray) {
                    self.beforeStoriesModelArray = [[NSMutableArray alloc] init];
                }
                [self.beforeStoriesModelArray addObject: beforeStoriesModel];
            //NSLog(@"%@", beforeStoriesModel.date);
    
            dispatch_async(dispatch_get_main_queue(), ^{
                [self sendViewStories: beforeStoriesModel];
                self.mainView.isLoading = NO;
                [self.mainView.tableView reloadData];
    
                NSMutableArray* storiesArray = [[NSMutableArray alloc] init];
                for (Stories* stories in beforeStoriesModel.stories) {
                    [storiesArray addObject: stories.id];
                }
                
                
                [[NSNotificationCenter defaultCenter] postNotificationName: @"upDateRight-Two" object:nil userInfo: @{@"value" : storiesArray}];
            });
            } failure:^(NSError * _Nonnull error) {
                NSLog(@"请求过往消息失败");
            }];
    }
    
    • 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

    但这里面遇到了数组越界问题,向右滑着滑着就访问到了不存在的索引,目前尚未解决
    请添加图片描述

    源码

    Github DEMO

    这里先挂一个半成品,之后会想办法解决这个bug,并尝试实现评论区、收藏夹和夜间模式等。

  • 相关阅读:
    7.MySQL复合查询
    SpringMVC_异常统一处理
    传统软件架构与微服务架构
    C程序设计(谭浩强)第五版课后题答案 第一章
    Mysql详细安装步骤
    浏览器数据库IndexedDB的使用
    Android Studio版本升级后的问题 gradle降级、jdk升级
    通过代码加解析的方式带领大家分析 :数组与指针的关系
    解析异常SAXParseExceptionis如何处理
    MyBatis—Spring 动态数据源事务的处理
  • 原文地址:https://blog.csdn.net/XY_Mckevince/article/details/134233543