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

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


目录

    • 第一周
      • 将网络请求封装在一个单例类Manager中
      • SDWebImage库的简单使用
      • 运用时间戳处理当前时间
      • 自定义NavigationBar
    • 第二周
      • 在UITableView的section之间的headerView上画UI
      • WKWebView的使用
      • WKWebView网页加载不出来
      • 线程问题
    • 第三周
      • attempting to add unsupported attribute: (null)错误
      • FMDB数据库的简单使用
      • WebView界面无限右滑
    • 源码


第一周

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

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

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

解析下来的数据用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"]];
}

运用时间戳处理当前时间

时间戳是指格林威治时间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

获取时间戳方法

//传入时间格式:20230917
+ (NSTimeInterval)getTimestampWithTimeString: (NSString *)timeString {
    NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat: @"YYYYMMdd"];
    
    NSDate* date = [dateFormatter dateFromString: timeString];
    
    NSTimeInterval timeStamp = [date timeIntervalSince1970];
    return timeStamp;
}

通过时间戳处理指定时间

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

自定义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];
}

这两个方法涉及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;
    }
}

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

 self.tableView.sectionHeaderTopPadding = 0;

WKWebView的使用

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

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

WKWebView网页加载不出来

大概查了一下

出现加载不出来本质原因是,如果没在plist文件中设置App Transport Security Settings的话,加载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];
            
        });

第三周

请添加图片描述

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

WebView界面无限右滑

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

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

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

//更新首页
[[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];
}

然后将更新后的数据传回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(@"请求过往消息失败");
        }];
}

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

源码

Github DEMO

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/117528.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS(二)—— 初识ArkTS开发语言(中)之ArkTS的由来和演进

前言 在上一篇文章HarmonyOS(二)—— 初识ArkTS开发语言(上)之TypeScript入门,我初识了TypeScript相关知识点,也知道ArkTS是华为基于TypeScript发展演化而来。 从最初的基础的逻辑交互能力,到…

VScode配置 github 上传代码

初始化,设置用户名和密码 # 设置你的 Git 用户名 git config --global user.name author# 设置你的 Git 邮箱 git config --global user.email authorgmail.com# 确保 Git 输出带有颜色 git config --global color.ui auto​# 查看 Git 配置 git list1. 初始化本地…

Java8实战-总结46

Java8实战-总结46 CompletableFuture:组合式异步编程让代码免受阻塞之苦使用 CompletableFuture 发起异步请求寻找更好的方案 CompletableFuture:组合式异步编程 让代码免受阻塞之苦 使用 CompletableFuture 发起异步请求 可以使用工厂方法supplyAsyn…

数据包端到端的流程

流程 A给F发送一个数据包的流程: 首先 A(192.168.0.1)通过子网掩码(255.255.255.0)计算出自己与 F(192.168.2.2)并不在同一个子网内,于是决定发送给默认网关(192.168.0.…

vue3项目实践

创建 vue3 项目 node本版:node 16.x.x, 脚手架:create-vue 脚手架工具,底层vite 创建vue3项目:npm init vuelatest setup函数 vue3 单文件组件 1、vite.config.js配置文件基于vite的配置 2、template模板不再要求唯…

4 函数的升级-下

重载(overload) 同一个标识符在不同的上下文有不同的意义 如汉语中“洗”和不同的字搭配后,有不同的含义,play 和不同的单词搭配后有不同的含义。 函数重载:用同一个函数名定义不同的函数,通过不同的参数搭…

c++11中的线程库和包装器

c11 1. 线程库1.1 线程库1.2 锁mutex 2. 包装器2.1 funciton2.2 bind 1. 线程库 1.1 线程库 C11中的线程库提供了一种方便的方式来创建和管理线程。其中,std::thread是一个重要的类,它允许我们创建新线程并控制它们的执行。以下是std::thread的一些重要…

048基于web+springboot的校园资料分享平台

欢迎大家关注,一起好好学习,天天向上 文章目录 一项目简介技术介绍 二、功能组成三、效果图四、 文章目录 一项目简介 本校园资料分享平台有管理员和用户两个角色。管理员功能有个人中心,学生管理,资料分享管理,资源分…

03、SpringBoot + 微信支付 ---- 创建订单、保存二维码url、显示订单列表

目录 Native 下单1、创建课程订单保存到数据库1-1:需求:1-2:代码:1-3:测试结果: 2、保存支付二维码的url2-1:需求:2-2:代码:2-3:测试:…

[python 刷题] 1248 Count Number of Nice Subarrays

[python 刷题] 1248 Count Number of Nice Subarrays 题目如下: Given an array of integers nums and an integer k. A continuous subarray is called nice if there are k odd numbers on it. Return the number of nice sub-arrays. 这道题和 1343 Number of S…

Python实验五 异常处理

实验 1:为下列代码添加异常处理。 xint(input(请输入一个整数)) print(100/x) # 实验 1:为下列代码添加异常处理。 try:xint(input(请输入一个整数:))print(100/x) except ValueError:print(请输入一个整数) except ZeroDivisionError:print…

Spring Boot中解决跨域问题(CORS)

1. 跨域介绍 首先解释什么是跨域,跨域就是前端和后端的端口号不同;会产生跨域问题,这里浏览器的保护机制(同源策略)。 同源策略:前端和后端的协议、域名、端口号三者都相同叫做同源。 我们看一下不同源&am…

项目部署文档

申请SSL证书 先申请,用免费的 下载证书 先将下载下来的保存起来 服务器安装JDK: 创建develop目录 mkdir /usr/local/develop/ 把JDK压缩包上传到/usr/local/develop/目录 解压安装包 并且将安装到指定目录 tar -zxvf /usr/local/develop/jdk-8u191-linux-x64.tar.gz -C /us…

嵌入式中如何将BootLoader与APP合并成一个固件

1、前言 嵌入式固件一般分为BootLoader和App,BootLoader用于启动校验、App升级、App版本回滚等功能,BootLoader在cpu上电第一阶段中运行,之后跳转至App地址执行应用程序。 因此,在发布固件的时候,会存在BootLoader固件…

IOS手机耗电量测试

1. 耗电量原始测试方法 1.1 方法原理: 根据iPhone手机右上角的电池百分比变化来计算耗电量。 1.2实际操作: 在iOS通用设置中打开电池百分比数值显示,然后操作30分钟,60分钟,90分钟,看开始时和结束时电池…

【WSL/WSL 2-Redis】解决Windows无法安装WSL Ubuntu子系统与Redis安装

前言 在现代计算环境中,开发人员和技术爱好者通常需要在不同的操作系统之间切换,以便利用各种工具和应用程序。在这方面,Windows用户可能发现WSL(Windows Subsystem for Linux)是一个强大的工具,它允许他们…

第六章 块为结构建模 P1|系统建模语言SysML实用指南学习

仅供个人学习记录 概述 块是SysML结构中的模块单元,用于定义一类系统、部件、部件互连,或者是流经系统的项,也用于定义外部实体、概念实体或其他逻辑抽象 块定义图用于定义块以及块之间的相互关系,如层级关系,也用于…

vue+elementUI 设置el-descriptions固定长度并对齐

问题描述 对于elementUI组件&#xff0c;el-descriptions 在以类似列表的形式排列的时候&#xff0c;上下无法对齐的问题。 问题解决 在el-descriptions 标签中&#xff0c;添加属性&#xff1a; :contentStyle"content_style" 控制其内容栏长度 <el-descripti…

【快速解决】Android Studio ERROR: Read timed out

目录 前言 回顾我查到过的解决方案&#xff08;这里是我自己解决时候的经历&#xff0c;赶时间的可以直接跳过看文章最后&#xff0c;快速进行解决&#xff09; 快速解决方案如下 总结 前言 当我们新建一个安卓项目出现Read timed out时候不要慌&#xff0c;这篇文章会打开…

PHP进销存ERP系统源码

PHP进销存ERP系统源码 系统介绍&#xff1a; 扫描入库库存预警仓库管理商品管理供应商管理。 1、电脑端手机端&#xff0c;手机实时共享&#xff0c;手机端一目了然。 2、多商户Saas营销版 无限开商户&#xff0c;用户前端自行注册&#xff0c;后台管理员审核开通 3、管理…