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

news2024/11/29 1:39:14

这几天一直在进行知乎日报的仿写,仿写过程中积累了许多实用的开发经验,并对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,并尝试实现评论区、收藏夹和夜间模式等。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

求极限Lim x->0 (x-sinx)*e-²x / (1-x)⅓

题目如下: 解题思路: 这题运用了无穷小替换、洛必达法则、求导法则 具体解题思路如下: 1、首先带入x趋近于0,可以得到(0*1)/0,所以可以把e的-x的平方沈略掉 然后根据无穷小替换,利用t趋近于0时&#xf…

容器核心技术-Namespace

一、容器 基于Linux 内核的 Cgroup, Namespace,以及Union FS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术,由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。 1.1 容器主要特性…

皮肤病辅助诊断软件,基于Android编写

1.系统介绍 编写的皮肤病辅助诊断软件,包括皮肤病识别、皮肤病区域分割、皮肤病信息介绍、识别历史记录查询、简单图像处理操作以及本机信息查询等功能 2.登录界面 运行之后首先显示登录界面 3.注册界面 注册一个账号 4.主界面 输入用户名密码点击登录按钮…

C语言——通讯录管理系统2.0版

对比之前C语言——通讯录管理系统初始版本,2.0版本有以下优化: 1.采用链表实现(之前版本是顺序表实现的,导致通讯录容量有限,现在使用链表实,实现了动态开辟空间,不浪费空间,也不会出…

用于3D Visual Grounding的多模态场景图

文章目录 引言方法1. Language Scene Graph Module Paper:《Free-form Description Guided 3D Visual Graph Network for Object Grounding in Point Cloud》【ICCV’2021】 Code:https://github.com/PNXD/FFL-3DOG 引言 3DVG任务有以下三个挑战&#x…

关于笔记平台的使用感受分享

关于笔记平台的使用感受分享 前言我用过的笔记平台笔记平台简单评价巴拉巴拉WPS文档/OneNote/TowerNotion/语雀各种博客平台 个人使用率最高的平台 前言 最近也有部分同学问我平常用的笔记平台是什么,以及我比较推荐的平台是什么。这里不是广告哈,因为我…

Spring AOP 简介

一、Spring AOP AOP 是一种思想,而 Spring AOP 是一个框架,提供了一种对 AOP 思想的实现。 1、什么是 AOP? AOP(Aspect Oriented Programming):是一种编程思想,表示面向切面编程。指的是对某…

<Vue>使用依赖注入的方式共享数据

什么是vue依赖注入? Vue是一个用于构建用户界面的渐进式框架。 它提供了一种简单而灵活的方式来管理组件之间的数据流,即依赖注入(Dependency Injection,DI)。 依赖注入是一种设计模式,它允许一个组件从另一…

【数组】有序数组的平方

## 977.有序数组的平方 力扣题目链接 (opens new window) 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 示例 1: 输入:nums [-4,-1,0,3,10]输出:[0,…

升级Python版本后,anaconda navigator启动失败

anaconda navigator启动失败,尤其是重装不解决问题的,大概率是库冲突 1.通过anaconda-navigator的图标启动,没有反应 2.在命令窗口,输入anaconda-navigator,报错如下 anaconda-navigator 3.错误来自这里 File &quo…

基于单片机的胎压监测系统的设计

收藏和点赞,您的关注是我创作的动力 文章目录 概要 一、系统整体设计方案二、 系统设计4.1 主流程图 三 系统仿真5.1 系统仿真调试实物 四、 结论 概要 本文以STC89C52单片机为控制核心,通过气压传感器模块对汽车各轮胎的胎压进行实时数据的采集与处理&…

AD教程 (七)元件的放置

AD教程 (七)元件的放置 第一种放置方法 点击右下角Panels,选择SCH Library,调出原理图库器件列表选中想要放置的元件,点击放置,就会自动跳转到原理图,然后放置即可这种方法需要不断打开元件库…

【源码解析】Spring Bean定义常见错误

案例1 隐式扫描不到Bean的定义 RestController public class HelloWorldController {RequestMapping(path "/hiii",method RequestMethod.GET)public String hi() {return "hi hellowrd";}}SpringBootApplication RestController public class Applicati…

立创eda专业版学习笔记(7)(阻焊开窗)

阻焊开窗是什么? 在介绍阻焊开窗之前,我们首先要知道阻焊层是什么。阻焊层是指印刷电路板子上要上油墨的部分,用于覆盖走线和敷铜,以保护PCB上的金属元素和防止短路。阻焊开窗是指在阻焊层上开一个口,以便在开口的位置…

前馈神经网络自动梯度计算和预定义算子

目录 1 自动梯度计算和预定义算子 1.1 利用预定义算子重新实现前馈神经网络 1.2 完善Runner类 1.3 模型训练 1.4 性能评价 1.5 增加一个3个神经元的隐藏层,再次实现二分类,并与1.1.1做对比. 1.6 自定义隐藏层层数和每个隐藏层中的神经元个数&#xf…

【网络安全技术】公钥密码体制

一、两种基本模型 1.加密模型 A要给B发信息,那就拿B的公钥加密,传给B,B收到后会拿他自己的私钥解密得到明文。 2.认证模型(数字签名) A用自己的私钥加密,传输之后,别人拿A的公钥解密&#xff…

IMU漂移相关

个人对IMU的漂移一直以来都很困惑,总结整理了这些材料,希望能理清楚一点思路。 总的来讲,IMU的漂移可建模为三部分,随机常值相关漂移白噪声, 但实际使用时,三者都出现的用法很少。严恭敏老师在博客中有相关…

cp没有--exclude选项!Linux复制文件夹时如何排除一些文件?

之前使用tar命令压缩文件将时,使用了–exclude选项排除了一些不需要的文件。现在我想复制一个文件夹,但是其中一些文件不需要复制,此时注意到cp命令居然没有–exclude选项。 rsync可以快速地帮助我们完成相同的事情,命令如下&…

Android Framework学习之Activity启动原理

Android Activity启动原理 Android 13.0 Activity启动原理逻辑流程图如下:

排序——选择排序

基本思想 每一趟在待排序元素中选取关键字最小的元素加入有序子序列。 算法代码 #include <iostream> using namespace std;//选择排序 void SelectSort(int nums[],int n){int i,j,min;for(i0;i<n-1;i){ //一共需要进行 n-1 趟 mini; //记录最小元素的下…