iOS: 仿新浪微博 OC (持续更新ing)
程序员文章站
2023-12-22 11:47:52
...
效果图(暂定)
1.配置AppIcon和LaunchImage
2.项目框架初建
需求:
多视图控制器
思路:
(1) 自定义一个继承UITabBarController的类作为window的rootViewController
(2) 给标签控制器上的每个子控制器包上一个根控制器
WBTabBarController.m
- (void)viewDidLoad {
[super viewDidLoad];
UITableViewController *homeVC = [self creatChildControllerWithClassName:@"WBHomeTableViewController" andTitle:@"首页" andImageName:@"tabbar_home"];
UITableViewController *findVC = [self creatChildControllerWithClassName:@"WBFindTableViewController" andTitle:@"发现" andImageName:@"tabbar_discover"];
UITableViewController *messageVC = [self creatChildControllerWithClassName:@"WBMessageTableViewController" andTitle:@"消息" andImageName:@"tabbar_message_center"];
UITableViewController *mineVC = [self creatChildControllerWithClassName:@"WBMineTableViewController" andTitle:@"我" andImageName:@"tabbar_profile"];
self.viewControllers = @[homeVC,messageVC,findVC,mineVC];
self.tabBar.tintColor = [UIColor orangeColor];
// KVC替换系统的tabBar
WBTabBar *tabBar = [[WBTabBar alloc] init];
tabBar.delegate =self;
[self setValue:tabBar forKey:@"tabBar"];
}
/**
创建标签栏子控制器
@param className 类名
@param title 标签标题
@param imageName 图标名称
@return 子控制器
*/
- (UITableViewController *)creatChildControllerWithClassName:(NSString *)className andTitle: (NSString *)title andImageName: (NSString *)imageName{
Class cla = NSClassFromString(className);
UITableViewController *vc = [[cla alloc] init];
// 标签名
vc.tabBarItem.title = title;
// 文字样式
NSMutableDictionary *titleAttrs = [NSMutableDictionary dictionary];
titleAttrs[NSForegroundColorAttributeName] = HWColor(123, 123, 123);
NSMutableDictionary *selectorTitleAttrs = [NSMutableDictionary dictionary];
selectorTitleAttrs[NSForegroundColorAttributeName] = [UIColor orangeColor];
[vc.tabBarItem setTitleTextAttributes:titleAttrs forState:UIControlStateNormal];
[vc.tabBarItem setTitleTextAttributes:selectorTitleAttrs forState:UIControlStateSelected];
// 图标
vc.tabBarItem.image = [UIImage imageNamed:imageName];
NSString *selectedImageName = [imageName stringByAppendingString:@"_selected"];
vc.tabBarItem.selectedImage = [[UIImage imageNamed:selectedImageName]imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
WBNavigationController *nav = [[WBNavigationController alloc] initWithRootViewController:vc];
[self addChildViewController:nav];
return nav;
}
WBNavigationController.m
这里用到一个单独抽取的UIBarButtonItem+Extension类,后面有写
+(void)initialize{
// 设置统一的item样式
UIBarButtonItem *item = [UIBarButtonItem appearance];
NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
textAttrs[NSForegroundColorAttributeName] = [UIColor orangeColor];
textAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:13];
[item setTitleTextAttributes:textAttrs forState:UIControlStateNormal];
// 设置不可用状态
NSMutableDictionary *disableTextAttrs = [NSMutableDictionary dictionary];
disableTextAttrs[NSForegroundColorAttributeName] = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:0.7];
disableTextAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:13];
[item setTitleTextAttributes:disableTextAttrs forState:UIControlStateDisabled];
}
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
if (self.viewControllers.count > 0) {
// 跳转后不是子控制器,则自动隐藏tabbar
viewController.hidesBottomBarWhenPushed = YES;
/* 设置导航栏上面的内容 */
// 设置左边的返回按钮
viewController.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithImageName:@"navigationbar_back" andTarget:self andSelector:@selector(back)];
// 设置右边的更多按钮
viewController.navigationItem.rightBarButtonItem = [UIBarButtonItem itemWithImageName:@"navigationbar_more" andTarget:self andSelector:@selector(more)];
}
[super pushViewController:viewController animated:animated];
}
// 返回
- (void)back
{
[self popViewControllerAnimated:YES];
}
// 更多
- (void)more
{
[self popToRootViewControllerAnimated:YES];
}
UIBarButtonItem+Extension.h
+ (instancetype) itemWithImageName:(NSString *) imageName andTarget:(id)target andSelector:(SEL)selector;
+ (instancetype) itemWithTitle:(NSString *) title andTarget:(id)target andSelector:(SEL)selector;
UIBarButtonItem+Extension.m
/**
* 创建一个item
*
* @param target 点击item后调用哪个对象的方法
* @param action 点击item后调用target的哪个方法
* @param image 图片
*
* @return 创建完的item
*/
+ (instancetype) itemWithImageName:(NSString *) imageName andTarget:(id)target andSelector:(SEL)selector{
UIBarButtonItem *item = [[self alloc] init];
// 创建按钮
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *image = [UIImage imageNamed:imageName];
[button setBackgroundImage: image forState:UIControlStateNormal];
NSString *highlightedImageName = [imageName stringByAppendingString:@"_highlighted"];
[button setBackgroundImage:[UIImage imageNamed:highlightedImageName] forState:UIControlStateHighlighted];
// 设置frame
button.frame = CGRectMake(0, 0, image.size.width, image.size.height);
// 设置事件
[button addTarget:target action:selector forControlEvents:UIControlEventTouchUpInside];
// item.customView = button;
return [[UIBarButtonItem alloc] initWithCustomView:button];
}
+ (instancetype) itemWithTitle:(NSString *) title andTarget:(id)target andSelector:(SEL)selector{
UIBarButtonItem *item = [[self alloc] init];
// 创建按钮
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:title forState:UIControlStateNormal];
UIColor *ItemNormalColor = [[UIColor alloc] initWithRed:80/255.0 green:80/255.0 blue:80/255.0 alpha:1];
UIColor *ItemHighlightedColor = [UIColor orangeColor];
[button setTitleColor:ItemNormalColor forState:UIControlStateNormal];
[button setTitleColor:ItemHighlightedColor forState:UIControlStateHighlighted];
// 尺寸
[button sizeToFit];
// 设置事件
[button addTarget:target action:selector forControlEvents:UIControlEventTouchUpInside];
// item.customView = button;
return [[UIBarButtonItem alloc] initWithCustomView:button];
}
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 创建窗口
_window = [[UIWindow alloc] init];
_window.frame = [UIScreen mainScreen].bounds;
// 设置根控制器
_window.rootViewController = [[WBTabBarController alloc] init];
// 显示窗口
[self.window makeKeyAndVisible];
return YES;
}
3.自定义tabBar
需求:
在 4 个控制器切换按钮中间增加一个撰写按钮
点击撰写按钮能够弹出发表微博的控制器
思路:
(1) 加号按钮的大小与其他 tabBarItem 的大小是一致的
(2) 添加加号按钮到TabBar中
(3) 遍历查找到其他4个UITabBarButton,设置宽度并调整位置
(4) 将撰写按钮放在自定义的UITabBar中间位置
(5) 在UITabBar内部监听撰写按钮的点击事件
WBTabBar.h
@class WBTabBar;
@protocol WBTabBarDelegate <UITabBarDelegate>
@optional
- (void)tabBarDidClickPlusButton:(WBTabBar *)tabBar;
@end
@interface WBTabBar : UITabBar
// 代理属性
@property (nonatomic, weak) id<WBTabBarDelegate> delegate;
@end
WBTabBar.m
@interface WBTabBar()
/// 加号按钮
@property(nonatomic,weak) UIButton *plusBtn;
@end
@implementation WBTabBar
-(instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
// 添加一个+号按钮 到tabbar中
UIButton *plusBtn = [[UIButton alloc] init];
// 设置背景
[plusBtn setBackgroundImage:[UIImage imageNamed:@"tabbar_compose_button"] forState:UIControlStateNormal];
[plusBtn setBackgroundImage:[UIImage imageNamed:@"tabbar_compose_button_highlighted"] forState:UIControlStateHighlighted];
// 增加+号图片
[plusBtn setImage:[UIImage imageNamed:@"tabbar_compose_icon_add"] forState:UIControlStateNormal];
[plusBtn setImage:[UIImage imageNamed:@"tabbar_compose_icon_add_highlighted"] forState:UIControlStateHighlighted];
// 大小
[plusBtn sizeToFit];
// 监听事件
[plusBtn addTarget:self action:@selector(plusClick) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:plusBtn];
self.plusBtn = plusBtn;
}
return self;
}
// 加号按钮点击事件
- (void)plusClick{
NSLog(@"加号按钮被点击了");
}
// 标签按钮布局
- (void)layoutSubviews{
[ super layoutSubviews ];
self.plusBtn.translatesAutoresizingMaskIntoConstraints = false;
//水平居中
NSLayoutConstraint *plusBtn_CenterX = [self.plusBtn.centerXAnchor constraintEqualToAnchor:self.centerXAnchor];
//垂直居中
NSLayoutConstraint *plusBtn_CenterY = [self.plusBtn.centerYAnchor constraintEqualToAnchor:self.centerYAnchor];
//宽度约束
NSLayoutConstraint *plusBtn_Width = [self.plusBtn.widthAnchor constraintEqualToConstant:64];
//高度约束
NSLayoutConstraint *plusBtn_Height = [self.plusBtn.heightAnchor constraintEqualToConstant:44];
[NSLayoutConstraint activateConstraints:@[plusBtn_CenterX,plusBtn_CenterY,plusBtn_Width,plusBtn_Height]];
/// 其他按钮尺寸及位置
// 按钮索引
CGFloat index = 0;
// 按钮宽度
CGFloat itemW = self.bounds.size.width / 5;
CGFloat itemH = self.bounds.size.height;
for (UIView * subView in self.subviews) {
if ([subView isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
// 设置宽度
subView.frame = CGRectMake(itemW * index, 0, itemW, itemH);
index += 1;
if (index == 2) {
index += 1;
}
}
}
}
@end
4.发现页面
(1) 导入cityinfo.json文件,创建城市模型数据
(2) 搜索框,使用UISearchController,NSPredicate实现
*WBCityInfoModel.h
@interface WBCityInfoModel : NSObject
/// 城市名
@property (nonatomic, copy) NSString *name;
/// id
@property (nonatomic, copy) NSString *idName;
/// 子城市名
@property (nonatomic, copy) WBCityInfoModel *children;
@end
*WBCityInfoModel.m
@implementation WBCityInfoModel
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
// jsonModel属性与系统重名
if ([key isEqualToString:@"id"]) {
_idName = value;
}
}
@end
*WBFindTableViewController.m
#import "WBFindTableViewController.h"
#import "WBCityInfoModel.h"
#import "YYModel.h"
#import "WBSearchResultController.h"
@interface WBFindTableViewController ()<UISearchResultsUpdating,UISearchControllerDelegate,UISearchBarDelegate>
// 模型数组
@property (nonatomic, strong) NSArray *modelArray;
// 结果展示控制器
@property (nonatomic, strong) WBSearchResultController *resultsTableController;
// 筛选结果集
@property (nonatomic, strong) NSArray *results;
@property (nonatomic, strong) UISearchController *searchContro;
// for state restoration
@property BOOL searchControllerWasActive;
@property BOOL searchControllerSearchFieldWasFirstResponder;
@end
@implementation WBFindTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = @"搜索";
// 加载数据
[self loadCitysData];
// 注册cell
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellID"];
[self setupUI];
}
- (void) setupUI{
// 结果控制器
WBSearchResultController *searchVC = [WBSearchResultController new];
searchVC.datas = [self.modelArray copy];
UISearchController *searchContro = [[UISearchController alloc]initWithSearchResultsController: searchVC];
// 在导航条显示
if (@available(iOS 11.0, *)) {
self.navigationController.navigationBar.prefersLargeTitles = YES;
self.navigationItem.searchController = searchContro;
} else {
self.tableView.tableHeaderView = searchContro.searchBar;
}
// 设置结果更新代理
searchContro.searchResultsUpdater = self;
[self.searchContro.searchBar sizeToFit];
//是否添加半透明覆盖层
searchContro.dimsBackgroundDuringPresentation = NO;
//是否隐藏导航栏
searchContro.hidesNavigationBarDuringPresentation = YES;
//如果不添加下面这行代码,在设置hidesNavigationBarDuringPresentation这个属性为YES的时候,搜索框进入编辑模式会导致,searchbar不可见,偏移-64;// know where you want UISearchController to be displayed
self.definesPresentationContext = YES;
searchContro.searchBar.placeholder = @"搜索";
self.resultsTableController.tableView.delegate = self;
self.searchContro = searchContro;
self.searchContro.delegate = self;
self.searchContro.searchBar.delegate = self; // so we can monitor text changes + others
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// restore the searchController's active state
if (self.searchControllerWasActive) {
self.searchContro.active = self.searchControllerWasActive;
_searchControllerWasActive = NO;
if (self.searchControllerSearchFieldWasFirstResponder) {
[self.searchContro.searchBar becomeFirstResponder];
_searchControllerSearchFieldWasFirstResponder = NO;
}
}
}
#pragma mark - UISearchBarDelegate
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];
}
#pragma mark - UISearchControllerDelegate
// Called after the search controller's search bar has agreed to begin editing or when
// 'active' is set to YES.
// If you choose not to present the controller yourself or do not implement this method,
// a default presentation is performed on your behalf.
//
// Implement this method if the default presentation is not adequate for your purposes.
//
- (void)presentSearchController:(UISearchController *)searchController {
}
- (void)willPresentSearchController:(UISearchController *)searchController {
// do something before the search controller is presented
}
- (void)didPresentSearchController:(UISearchController *)searchController {
// do something after the search controller is presented
}
- (void)willDismissSearchController:(UISearchController *)searchController {
// do something before the search controller is dismissed
}
- (void)didDismissSearchController:(UISearchController *)searchController {
// do something after the search controller is dismissed
}
#pragma mark - UISearchResultsUpdating方法
-(void)updateSearchResultsForSearchController:(UISearchController *)searchController{
WBSearchResultController *vc = searchController.searchResultsController;
// 设置searchController的代理为当前控制器,用于搜索结果页点击cell跳转
vc.tableView.delegate = self;
NSString *inputStr = searchController.searchBar.text ;
if (searchController.searchBar.text != @"" || vc.datas != nil || vc.datas.count > 0) {
// 使用谓词过滤数据
NSPredicate *pred = [NSPredicate predicateWithFormat:@"name CONTAINS %@",inputStr];
// 取出包含‘inputStr’的元素
self.results = [self.modelArray filteredArrayUsingPredicate:pred];
// 把过滤完的数据传给控制器
vc.datas = self.results;
// 刷新数据
[vc.tableView reloadData];
}else{
return;
}
}
#pragma mark - cell点击事件
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// 判断点击的cell模型是当前tableView的还是结果tableView的
// 取得该cell的模型
WBCityInfoModel *city = tableView == self.tableView ? self.modelArray[indexPath.row] : self.results[indexPath.row];
// 跳转页标题
WBSearchResultController *vc = [[WBSearchResultController alloc] init];
vc.title = city.name;
NSLog(@"被点击了%@",city.name);
vc.view.backgroundColor = [UIColor yellowColor];
// push to resultsTableController
[self.navigationController pushViewController:vc animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.modelArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath];
// 设置数据
WBCityInfoModel *info = self.modelArray[indexPath.row];
cell.textLabel.text = info.name;
return cell;
}
#pragma mark - 加载数据
- (void)loadCitysData {
NSString *url = [[NSBundle mainBundle]URLForResource:@"citysData.json" withExtension:nil];
//加载JSON文件
NSData *data = [NSData dataWithContentsOfURL:url];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// YYModel实现字典数组转模型数组
self.modelArray = [NSArray yy_modelArrayWithClass:[WBCityInfoModel class] json:dict];
// 回到主线程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadData];
}];
}
@end
*WBSearchResultController.h
#import <UIKit/UIKit.h>
#import "WBCityInfoModel.h"
@interface WBSearchResultController : UITableViewController
// 接收模型属性
@property (nonatomic, strong)NSArray *datas;
@end
*WBSearchResultController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"searchReasultCellID"];
}
#pragma mark - 数据源方法
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.datas.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"searchReasultCellID" forIndexPath:indexPath];
// 设置数据
WBCityInfoModel *info = self.datas[indexPath.row];
cell.textLabel.text = info.name;
return cell;
}