【iOS】知乎日报

news/2024/7/20 22:45:21 标签: ios, xcode, objective-c, iphone

文章目录

  • 前言
  • 一、首页
    • 1.网络的异步请求
    • 2.避免同一网络请求执行多次
    • 3.下拉刷新与上拉加载的实现
      • 下拉刷新
      • 上拉加载
  • 二、网页
    • 1.webView的实现
    • 2.webView的滑动加载
    • 3.网页与首页内容的同步更新
  • 三、评论区
    • Masonory实现行高自适应
  • 四、收藏中心
    • 通过FMDB实现数据持久化
      • 1.创建或打开数据库
      • 2.数据库操作
        • 建表操作
        • 增加数据
        • 删除数据
        • 查询数据
        • 修改数据库字段
  • 总结


前言

近期耗时一个月完成了第一个项目知乎日报,用到了AFNetworkingJSONModelMasonryFMDB等一系列第三方库以及其他新学的知识点,整体采用了MVC框架进行编写,特此撰写博客总结

一、首页

实现效果:
在这里插入图片描述
我们通过我们的视图层级图展开我们首页功能的分析:
在这里插入图片描述

1.网络的异步请求

我们从上述层级示意图以及动画效果展示,我们可以看出这个页面主要是将轮播图Tableviewcell共同添加到一个TableView上实现的页面。

在这里我们用到了知乎日报提供的开源接口实现我们对图片以及数据的请求——知乎日报 API 分析(如何规范api设计)

在这里笔者是使用AFNetworking对API进行请求的。详见【iOS】使用单例封装通过AFNetworking实现的网络请求

在刚开始请求数据来构建页面时,笔者请求到的数据总是为空,因此View中总是无法显示请求的图片以及数据,或是请求到了数据,但并没有在View中显示出来,细分析其原因,是因为网络请求是一个异步且耗时的过程。笔者给出一个例子:

- (void)getCurrentModel {
    [[Manager shareManager] NetWorkGetWithRecentData:^(CurrentModel *model) {
        self.modelDictionary = [model toDictionary];
        
        for (int i = 0; i < 5; i++) {
            [self->_topURLArray addObject:self.modelDictionary[@"top_stories"][i][@"url"]];
            [self->_topIDArray addObject:self.modelDictionary[@"top_stories"][i][@"id"]];
            [self.topPageTitleArray addObject:self.modelDictionary[@"top_stories"][i][@"title"]];
            [self.topPageImageURLArray addObject:self.modelDictionary[@"top_stories"][i][@"image"]];
        }
        [self.topArray addObject:[model toDictionary]];
        [self.allArray addObject:[model toDictionary]];

        // 异步执行任务创建方法
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.homeView.allArray addObject:[model toDictionary]];

        });

    } andError:^(NSError * _Nullable error) {
            NSLog(@"失败");
        }];
    NSLog(@"%@", _topArray);
    NSLog(@"%@", _allArray);
    [self initHomeView];

}

在这段代码中我们先用[self initHomeView]对我们的视图进行初始化,再去请求我们的数据对视图进行赋值,结果就会出现这样的页面
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

可以看到我们打印的结果为空,并且View中的控件没有被赋值,这就是涉及到了异步请求的知识。简单的来说就是当我们使用NetWorkGetWithRecentData:方法时,我们的网络请求被放在了后台线程进行执行,但这并不会阻塞我们的主线程,我们的程序的主线程会继续执行。

也就是说即使我们的网络请求还没有请求成功,我们的程序也会继续向下执行,这就导致了我们的View的空白

使用AFNetworking等网络请求库时,它们通常会处理底层的多线程操作,将网络请求放在后台线程中执行,以确保不会阻塞主线程。这是为了避免在主线程中执行网络请求导致的界面卡顿和不流畅的用户体验。

现在我们利用代码来验证一下这个结论:

- (void)getCurrentModel {
    NSLog(@"111");
    [[Manager shareManager] NetWorkGetWithRecentData:^(CurrentModel *model) {
        self.modelDictionary = [model toDictionary];
        
        for (int i = 0; i < 5; i++) {
            [self->_topURLArray addObject:self.modelDictionary[@"top_stories"][i][@"url"]];
            [self->_topIDArray addObject:self.modelDictionary[@"top_stories"][i][@"id"]];
            [self.topPageTitleArray addObject:self.modelDictionary[@"top_stories"][i][@"title"]];
            [self.topPageImageURLArray addObject:self.modelDictionary[@"top_stories"][i][@"image"]];
        }
        [self.topArray addObject:[model toDictionary]];
        [self.allArray addObject:[model toDictionary]];

        // 异步执行任务创建方法
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"222");
            [self initHomeView];
            [self.homeView.allArray addObject:[model toDictionary]];

        });

    } andError:^(NSError * _Nullable error) {
            NSLog(@"失败");
        }];
    NSLog(@"333");
    
}

假如我们这段代码都在一个线程中执行,那么1,2,3会相继输出

但是结果并非如此
在这里插入图片描述
在网络请求中的NSLog(@"222");是最后才输出的,这也证明了我们的网络请求是一个异步的过程。


说完了问题,我们该如何解决这个问题呢,这里就需要使用到我们的GCD—— dispatch_async(dispatch_get_main_queue(), ^{ });

使用这个方法,能在其他线程中调用我们的主线程,也就是当后台线程的进程执行完之后,返回我们的主线程进行任务执行
这就保证了当我们的网络请求请求完之后我们才会对我们的UI进行布局,防止了UI出现空白的情况。

关于GCD的知识笔者还不甚了解,后续学到会加以补充,现在只懂简单的使用与其基本原理


2.避免同一网络请求执行多次

在进行下拉加载数据时,笔者使用了- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;协议来监听tableView.contentOffset以此来判断是否需要获取新数据。
但是网络请求是一个耗时的过程,假如我们连续滑动页面,就会出现同一网络请求被执行多次的情况,我们的程序会因为线程阻塞而崩溃。为了避免这种情况,我们就需要对网络请求进行锁定。
笔者在代码中使用了一个BOOL变量对网络请求进行锁定,当监听到需要加载数据的时候,我们的BOOL变量就为YES,请求成功数据后再变为NO,在BOOL变量为YES时无法进行相同的网络请求,这就避免了同一网络请求执行多次。

- (void)getPastModel:(NSString *)date {
    if (self.isLoadingMoreData) {
           return;
       }
    self.isLoadingMoreData = YES; // 设置加载标志
    pastData++;
    numbersOfLoad++;
    date = [self.homeModel pastDateForJson:numbersOfLoad];
    [[Manager shareManager] NetWorkGetWithPastData:^(PastModel * _Nonnull model) {
        self.pastModelDictionary = [model toDictionary];
//        [self.homeView.pastArray addObject:self.pastModelDictionary];
        [self.homeView.pastTimeArray addObject:[self.homeModel pastDate:numbersOfLoad]];
        [self.homeView.allArray addObject:[model toDictionary]];
        [self.allArray addObject:[model toDictionary]];
        
        NSLog(@"%@", self.allArray);
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.homeView hideLoadMoreView];
            [self.homeView.tableView reloadData];
            self.isLoadingMoreData = NO; // 重置加载标志
        });
    } andError:^(NSError * _Nullable error) {
        NSLog(@"失败: %@", error); // 添加日志
    } andDate:date];

}

3.下拉刷新与上拉加载的实现

通过首页的动画演示,我们可以看到在笔者上拉与下拉时都有对应的菊花控件显示,在这里笔者分别用了两种方法去使用菊花控件在这里插入图片描述

下拉刷新

笔者使用了MJRefresh第三方库进行菊花控件的创建,使用第三方库的创建十分简单,只有一行代码 self.homeView.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)];

随后我们使用loadNewData方法模拟加载效果:

- (void)loadNewData {
    // 模拟网络请求加载新数据
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 加载完成后结束下拉刷新
        [self.homeView.tableView.mj_header endRefreshing];
        // 刷新 UITableView 的数据
//        [self.homeView.tableView reloadData];
    });
}

上拉加载

对于上拉加载我们同样也可以使用MJRefresh,但是笔者在这里使用了UIKit自带的控件UIActivityIndicatorView

首先在上拉时创建并展现菊花控件

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    // 用户结束拖拽手势
    if (self.tableView.contentOffset.y + self.tableView.frame.size.height >= self.tableView.contentSize.height) {
//        self.backgroundColor = [UIColor whiteColor];
        [self showLoadMoreView];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"reNew" object:nil userInfo:nil];
//        NSLog(@"%d", ([self.viewModelDictionary[@"stories"] count] + 1) * pastData);
//        [_tableView reloadData];
    }
}

- (void)showLoadMoreView {
    // 创建和配置加载动画视图
    self.activityIndicator.frame = CGRectMake(0, 0, 320, 33);
    self.activityIndicator.tag = 123; // 设置一个标记以便后续移除
    [self.activityIndicator startAnimating];
    // 将加载动画视图添加到UIScrollView的底部
    self.tableView.tableFooterView = self.activityIndicator;
}

- (void)hideLoadMoreView {
    // 停止加载动画
    UIActivityIndicatorView *activityIndicator = [self.tableView.tableFooterView viewWithTag:123];
    [activityIndicator stopAnimating];
    
    // 隐藏加载动画视图
    self.tableView.tableFooterView = nil;
}

随后在网络请求完成后在主线程移除菊花控件的展现

- (void)getPastModel:(NSString *)date {
    if (self.isLoadingMoreData) {
           return;
       }
    self.isLoadingMoreData = YES; // 设置加载标志
    pastData++;
    numbersOfLoad++;
    date = [self.homeModel pastDateForJson:numbersOfLoad];
    [[Manager shareManager] NetWorkGetWithPastData:^(PastModel * _Nonnull model) {
        self.pastModelDictionary = [model toDictionary];
//        [self.homeView.pastArray addObject:self.pastModelDictionary];
        [self.homeView.pastTimeArray addObject:[self.homeModel pastDate:numbersOfLoad]];
        [self.homeView.allArray addObject:[model toDictionary]];
        [self.allArray addObject:[model toDictionary]];
        
        NSLog(@"%@", self.allArray);
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.homeView hideLoadMoreView];
            [self.homeView.tableView reloadData];
            self.isLoadingMoreData = NO; // 重置加载标志
        });
    } andError:^(NSError * _Nullable error) {
        NSLog(@"失败: %@", error); // 添加日志
    } andDate:date];

}

二、网页

网页效果实现
在这里插入图片描述

1.webView的实现

在我们的知乎日报API中,其给我们提供了webView的URL,我们可以通过使用WKWebView来加载我们的网页

根据MVC的原则,我们会在首页的Viewcontroller中获取我们的URL然后将其传到我们网页的Viewcontroller中,最后在View中加载我们的网页

步骤1:
导入#import <UIKit/UIKit.h>

步骤2:

 _webView = [[WKWebView alloc] init];
 NSURL* urlWeb = [NSURL URLWithString:_allArray[(_nowPage - 5) / 5][@"stories"][_nowPage % 5][@"url"]];
 NSURLRequest* webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];
 [_webView loadRequest:webRequest];

这样我们就完成了我们webView的加载


2.webView的滑动加载

同时通过动画我们可以看到每向后滑动一页,我们的webView才会进行加载,这就节约了我们的内存,减少了用户等待的时间。

笔者这里的思路是通过协议监听当前scrollerView的页数,当当前网页没有加载过便用通知传值通知View层进行Webview的加载

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    NSInteger currentPage = (scrollView.contentOffset.x / Width) + 0.5;
    _pageNumber = [NSNumber numberWithInteger:currentPage + 5];
    
    //滑动cell只加载当前webview,不加载多余webView
    if (scrollView.tag == 77) {

        // 当滚动视图向右滚动且快接近画布右边缘时,触发加载数据的操作
        if (scrollView.contentOffset.x >  ([_allArray count] * 5 * Width  - Width) && self.isLoadingMoreData == NO) {
            self.isLoadingMoreData = YES;
            
            [self loadMoreData];
        } else {
            if (currentPage != (_webView.nowPage - 5) && isLoadingWebView == NO && self.isLoadingMoreData == NO && ![self.nowPageSet containsObject:_pageNumber]) {
                NSLog(@"%ld", (long)currentPage);
                isLoadingWebView = YES;
                _webView.nowPage = currentPage + 5;
                [self.nowPageSet addObject:_pageNumber];
                NSLog(@"%ld..", (long)_webView.nowPage);
                [[NSNotificationCenter defaultCenter] postNotificationName:@"newPage" object:nil userInfo:nil];
            }
        }
    }

}

- (void)addView {
//    NSLog(@"%@111",_allArray);
    if (_isCollectionWebView == YES) {
        _scrollView.tag = 55;
        _scrollView.contentOffset = CGPointMake(Width * _nowPage, 0);
        self.scrollView.contentSize = CGSizeMake(Width * [_webURLArray count], 0);
        NSLog(@"%@111", _webURLArray);
        for (int i = 0; i < [_webURLArray count]; i++) {
            _webView = [[WKWebView alloc] init];
            NSURL* urlWeb = [NSURL URLWithString:_webURLArray[i]];
            NSURLRequest* webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];
            [_webView loadRequest:webRequest];
            _webView.frame = CGRectMake(Width * i, 0, Width, Height * 0.92);
            [self.scrollView addSubview:_webView];
        }
    } else {
        if (_nowPage < 5) {
            _scrollView.tag = 66;
            _scrollView.contentOffset = CGPointMake(Width * _nowPage, 0);
            self.scrollView.contentSize = CGSizeMake(Width * 5, 0);
            for (int i = 0; i < 5; i++) {
                _webView = [[WKWebView alloc] init];
                NSURL* urlWeb = [NSURL URLWithString:_allArray[0][@"top_stories"][i][@"url"]];
                NSURLRequest* webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];
                [_webView loadRequest:webRequest];
                _webView.frame = CGRectMake(Width * i, 0, Width, Height * 0.92);
                [self.scrollView addSubview:_webView];
            }
        } else {
            _scrollView.tag = 77;
            self.scrollView.contentSize = CGSizeMake(Width * [self.allArray count] * 5 + Width, 0);
            _scrollView.contentOffset = CGPointMake(Width * (_nowPage - 5), 0);
            _webView = [[WKWebView alloc] init];
            NSURL* urlWeb = [NSURL URLWithString:_allArray[(_nowPage - 5) / 5][@"stories"][_nowPage % 5][@"url"]];
            NSURLRequest* webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];
            [_webView loadRequest:webRequest];
            _webView.frame = CGRectMake(Width * (_nowPage - 5), 0, Width, Height * 0.875);
            [self.scrollView addSubview:_webView];
        }
    }

}

同时笔者为了实现webView的流畅滑动,在滑动到最后一页需要请求新数据时,笔者将 self.scrollView.contentSize 设为了CGSizeMake(Width * [self.allArray count] * 5 + Width, 0);

[self.allArray count]在这里也会实时更新,请求到新数据之后新数据会添加到[self.allArray count]中

在这里+Width的意义就是当我们滑动到最后一页时,我们需要时间去等待数据的加载,如果没有多处一页空白,那么只有当数据加载完成后我们的scrollerView才会滑动到下一页。
+Width则实现了无需等待,我们可以直接滑动到下一页,数据加载完成后会自动呈现在我们的scrollerView

- (void)layoutNewScrollView {
    self.scrollView.contentSize = CGSizeMake(Width * [self.allArray count] * 5 + Width, 0);
}

3.网页与首页内容的同步更新

当我们在网页页面滑动获取新数据的同时,我们返回首页时数据也需要同步更新。

这里笔者的思路是通过设置全局变量与通知传值来告诉首页我们网页的数据已经更新,同时将网页的数组同步给首页的数组,从而实现数据的同步更新。

##网页
- (void)pressButtonReturn {
    if (_isCollectionWebView == NO) {
        NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
        userInfo[@"key1"] = self.allArray;
        userInfo[@"key2"] = self.pastTimeArray;
        [self.navigationController popViewControllerAnimated:YES];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"update" object:nil userInfo:userInfo];
    }
    else {
        [self.navigationController popViewControllerAnimated:YES];
    }
}

##首页
- (void)update:(NSNotification *)send {
    NSArray *newDataArray = send.userInfo[@"key1"];
    NSArray *newPastTimeArray = send.userInfo[@"key2"];
    
    if (![self.homeView.allArray isEqualToArray:newDataArray] || ![self.homeView.pastTimeArray isEqualToArray:newPastTimeArray]) {
        self.homeView.allArray = [newDataArray mutableCopy];
        self.allArray = [newDataArray mutableCopy];
        self.homeView.pastTimeArray = [newPastTimeArray mutableCopy];
        
        [self.homeView.tableView reloadData];
    }
    //    self.homeView.allArray = send.userInfo[@"key1"];
	//    self.allArray = send.userInfo[@"key1"];
	//    self.homeView.pastTimeArray = send.userInfo[@"key2"];
    //原来的这段代码让self.homeView.allArray与self.allArray同时指向了send.userInfo[@"key1"],也就是说我之后在此对self.allArray进行修改self.homeView.allArray也会同步修改,反之亦然
}

对于如何获取全局变量可以参考以下博客:【iOS】浅析static,const,extern关键字

三、评论区

实现效果:
在这里插入图片描述

Masonory实现行高自适应

这个知识点笔者会专门再写一篇博客讲述,后面会将博客补上

四、收藏中心

实现效果:
在这里插入图片描述

通过FMDB实现数据持久化

在使用FMDB之前,我们简单了解一下什么是FMDB

FMDB 是 iOS 平台的 SQLite 数据库框架
FMDB 以 OC 的方式封装了 SQLite 的 C 语言 API

更通俗的讲,FMDB就是iOS的数据库,它支持SQL语句来执行操作

核心类 FMDB有三个主要的类

FMDatabase

一个FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句。

FMResultSet

使用FMDatabase执行查询后的结果集

FMDatabaseQueue

用于在多线程中执行多个查询或更新,它是线程安全的

笔者在这里简单介绍一下FMDB的使用步骤:

1.创建或打开数据库

 - (void)createDataBase {
    // 获取数据库文件的路径
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *path = [docPath stringByAppendingPathComponent:@"DailyNews.sqlite"];
    FMDatabase *db = [FMDatabase databaseWithPath:path];
    if ([db open]) {
        NSLog(@"Open database Success");
    } else {
        NSLog(@"fail to open database");
    }
}

在这里我们的path会有三种情况:

  • 具体文件路径
    如果不存在会自动创建,(使用绝对路径),就像我们的此时的path为[docPath stringByAppendingPathComponent:@"DailyNews.sqlite"];
  • 空字符串 @""
    会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,数据库文件也被删除。
  • nil
    会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁

2.数据库操作

在FMDB中,除查询以外的所有操作,都称为“更新”

  • create
  • drop
  • insert
  • update
  • delete
建表操作

语法:create table table_name (field1 type1, field2 type2, field3 type3...);

在SQLite3中可以不指定字段的数据类型,SQLite3会自动推断类型。常用的格式有:
文本: Text
整形: Integer
二进制数据: Blob
浮点型: Real Float、Double
布尔型: Boolean
时间型: Time
日期型: Date
时间戳: TimeStamp

代码示例:

if ([db open]) {
        BOOL result = [db executeUpdate:@"CREATE TABLE 't_DailyNews' ('webPageID' TEXT, 'webPageTitle' TEXT, 'webPageImageURL' TEXT, 'webURL' TEXT)"];
        if (result) {
            NSLog(@"创表成功");
        }
        NSLog(@"Open database Success");
    } else {
        NSLog(@"fail to open database");
    }
增加数据

语法:INSERT INTO table_name (field1, field2...) VALUES (var1, var2...);

代码示例:

- (void)insertDataBase {
    if ([_dataBase open]) {
        NSString *insertSql = @"insert into 't_DailyNews'(webPageID,webPageTitle,webPageImageURL, webURL) values(?,?,?,?)";
        BOOL result = [_dataBase executeUpdate:insertSql, _webPageID, _webPageTitle, _webPageImageURL, _webURL];
        if (result) {
            NSLog(@"添加数据成功");
        } else {
            NSLog(@"添加数据失败");
        }
    }
}
删除数据

语法:NSString *deleteSQL = @"DELETE FROM your_table WHERE condition = ?";
删除所有符合条件的数据,不设置条件时清空所有数据。

代码示例:

- (void)deleteDataBase {
    NSString *sql = @"delete from t_DailyNews where (webPageID) = (?) and (webPageTitle) = (?) and (webURL) = (?)";
    if ([_dataBase open]) {
        BOOL result = [_dataBase executeUpdate:sql, _webPageID, _webPageTitle, _webURL];
        if (result) {
            NSLog(@"删除数据成功");
        } else {
            NSLog(@"删除数据失败");
        }
    }
}
查询数据

语法:NSString *querySQL = @"SELECT * FROM your_table WHERE condition = ?";

代码示例:

- (void)traverseDataBase {
    [_dataBase open];
    NSString *selectSql = @"select * from t_DailyNews";
    FMResultSet *rs = [_dataBase executeQuery:selectSql];
    //FMResultSet专门用来进行查询操作
    while ([rs next]) {
        NSString *webPageID = [rs stringForColumn:@"webPageID"];
        NSString *webPageTitle = [rs stringForColumn:@"webPageTitle"];
        NSString *webURL = [rs stringForColumn:@"webURL"];
        if ([webPageID isEqualToString:_webPageID] && [webPageTitle isEqualToString:_webPageTitle] && [webURL isEqualToString:_webURL]) {
            _webView.buttonCollection.selected = YES;
            [_dataBase close];
            return;
        }
    }
    _webView.buttonCollection.selected = NO;
    [_dataBase close];
    return;
}
修改数据库字段

添加列:

ALTER TABLE your_table
ADD COLUMN new_column_name INTEGER;

删除列:

ALTER TABLE your_table
DROP COLUMN column_to_be_deleted;

重命名列:

ALTER TABLE your_table
RENAME COLUMN old_column_name TO new_column_name;

笔者这里仅给出添加列的示例:

- (void)xiugai {
    // 获取数据库文件的路径
       NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
       NSString *path = [docPath stringByAppendingPathComponent:@"DailyNewsOfGood.sqlite"];
       FMDatabase *db = [FMDatabase databaseWithPath:path];
       
    // Assuming db is your FMDatabase instance
    if ([db open]) {
        NSString *alterTableSQL = @"ALTER TABLE 't_DailyNewsOfGood' ADD COLUMN countsOfGood TEXT";
        BOOL success = [db executeUpdate:alterTableSQL];

        if (success) {
            NSLog(@"Table altered successfully");
        } else {
            NSLog(@"Error altering table: %@", [db lastErrorMessage]);
        }

        [db close];
    } else {
        NSLog(@"Error opening database: %@", [db lastErrorMessage]);
    }

}

这里需要注意的是如果我们需要修改数据库中表的字段,不能在创表的代码上进行修改,因为此时数据库已经创建成功


讲完了数据库的基本操作,我们来讲讲我们的收藏效果是如何实现的

当我们点击我们的收藏按钮时,就将当前页面的webView与ID等一系列标志添加到数据库中,当我们打开收藏中心时我们的程序首先会读取数据库中的内容,从而将我们收藏的内容展现在我们UI上。

反之删除也是同理,我们取消收藏时就以当前页面的webView与ID等一系列标志与数据库中的数据进行匹配,如果匹配成功则成功删除数据库中对应的数据。从而实现了收藏的取消

同时我们在滑动我们的webView时还会通过遍历数据库实时对当前webView是否存在于我们的数据库中进行判断

- (void)judgeCollectionInOrOut {
    [_dataBase open];
    NSString *selectSql = @"select * from t_DailyNews";
    FMResultSet *rs = [_dataBase executeQuery:selectSql];
    //FMResultSet专门用来进行查询操作
    while ([rs next]) {
        NSString *webPageID = [rs stringForColumn:@"webPageID"];
        NSString *webPageTitle = [rs stringForColumn:@"webPageTitle"];
        NSString *webURL = [rs stringForColumn:@"webURL"];
        if ([webPageID isEqualToString:_webPageID] && [webPageTitle isEqualToString:_webPageTitle] && [webURL isEqualToString:_webURL]) {
            _webView.buttonCollection.selected = YES;
            [_dataBase close];
            return;
        }
    }
    _webView.buttonCollection.selected = NO;
    [_dataBase close];
    return;
}

总结

知乎日报耗时一个月,在此期间碰到了许多问题,例如网络请求的异步加载问题,Masonry实现行高自适应的控件约束问题,单元格内按钮的复用问题,tableview的优化问题以及使用MVC框架中各个层的职责问题,总结便是学习iOS之路任重道远,还需更加努力


http://www.niftyadmin.cn/n/5205629.html

相关文章

java:application.properties的详细使用以及区分环境

文章目录 什么是application.properties文件&#xff1f;如何在Java中使用application.properties文件&#xff1f;将数据注入到 Bean 中使用自定义的配置文件使用命令行参数进行配置配置文件的优先级加载外部的配置文件多环境配置1、创建配置文件2、在 application.properties…

安防视频EasyCVR平台太阳能供电+4G摄像头视频监控方案的建设

在工地、光伏、风电站、水库河道等场景中&#xff0c;以及一些偏远地区的项目现场&#xff0c;会存在无网无电情况&#xff0c;大大制约了视频监控系统建设的效率及可行性。在这种场景中&#xff0c;我们也可以通过太阳能供电4G监控摄像机的方案&#xff0c;满足偏远地区无网无…

qt双击treeview节点之后,完成编辑,获取完成编辑得信号

Qt 中&#xff0c;要获取 QTreeView 节点完成编辑的信号&#xff0c;您可以使用 QTreeView 的 doubleClicked 信号以及重写相应的编辑触发函数。通常情况下&#xff0c;完成编辑后会触发 closeEditor 信号。 以下是一个示例代码&#xff0c;演示了如何实现这个功能&#xff1a…

80基于matlab的小波包熵与模糊C均值聚类的故障诊断,以凯斯西储大学轴承数据为例进行分析

基于matlab的小波包熵与模糊C均值聚类的故障诊断&#xff0c;以凯斯西储大学轴承数据为例进行分析。对数据进行小波包分解后重构&#xff0c;然后提取各频带能量分布&#xff0c;后计算小波包熵进行故障诊断。输出特征可视化结果。数据可更换自己的&#xff0c;程序已调通&…

有哪些好用的CFD软件?怎么选?

ANSYS Fluent和COMSOL Multiphysics以及OpenFOAM这3款CFD软件哪个好&#xff1f;cfd软件中哪款最实用&#xff1f;cfd软件有哪些&#xff1f;今天就给大家带来这几款CFD软件对比分析&#xff0c;一起来看看吧。 ANSYS Fluent ANSYS Fluent 是一种流行的计算流体动力学 (CFD) …

c语言:用迭代法解决递归问题

题目&#xff1a; 解释&#xff1a;题目的意思就是用迭代法的空间和时间复杂的太高了&#xff0c;需要我们减小空间与时间的复杂度&#xff0c;我就想到了迭代法&#xff0c;思路和代码如下&#xff1a; #include <stdio.h> //这里是递归法转迭代法 int main() {int x,i…

[Unity+OpenAI TTS] 集成openAI官方提供的语音合成服务,构建海王暖男数字人

1.简述 最近openAI官方发布了很多新功能&#xff0c;其中就包括了最新发布的TTS语音合成服务的api接口。说到这个语音合成接口&#xff0c;大家可能会比较陌生&#xff0c;但是说到chatgpt官方应用上的聊天机器人&#xff0c;那个台湾腔的海王暖男的声音&#xff0c;可能就有印…

10个容易被忽视的FastAPI实用功能

大家好&#xff0c;FastAPI是一种现代、高性能的Python Web框架&#xff0c;用于构建Web应用程序和API。它基于Python的异步编程库asyncio和await语法&#xff0c;以及类型注解和自动文档生成等特性&#xff0c;提供了快速、易用和可靠的开发体验&#xff0c;接下来本文将介绍1…