08-百思不得姐(第八天)
2016-04-17 10:03
190 查看
一、点击顶部的状态栏,scrollView会自动回滚到顶部
实现思路:
自定义一个View模型
提供显示顶部窗口的接口
在接口方法里面创建window,添加到顶部
给window添加点击手势,点击后scrollView回滚到顶部
1> 添加view模型,提供接口
@interface DSBackTopWindow : NSObject /** * 显示window */ + (void)show; /** * 隐藏window */ + (void)hide; @end
2> 实现接口方法
创建window,并且添加手势,只需要创建一次,所以写在initialize方法里
+ (void)initialize { window_ = [[UIWindow alloc] init]; window_.frame = CGRectMake(0, 0, DSScreenW, DSSystemStatusBarH); window_.windowLevel = UIWindowLevelAlert; window_.backgroundColor = [UIColor clearColor]; [window_ addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(topWindowClick)]]; }
接口实现
/** * 显示 */ + (void)show { window_.hidden = NO; } /** * 隐藏 */ + (void)hide { window_.hidden = YES; }
window点击手势监听方法的实现(首先找到keyWindow里面的scrollView,然后让其滚回到顶部)
/** * 顶部window点击(scrollView回滚到顶部) */ + (void)topWindowClick { UIWindow *window = [UIApplication sharedApplication].keyWindow; [self searchScrollViewInSuperView:window]; } /** * 寻找主窗口的scrollView子控件(递归) */ + (void)searchScrollViewInSuperView:(UIView *)superView { for (UIScrollView *subView in superView.subviews) { // 只有显示在主窗口上,而且类型是scrollView才需要移动 if ([subView isKindOfClass:[UIScrollView class]] && subView.isShowingOnkeyWindow) { CGPoint offset = subView.contentOffset; offset.y = -subView.contentInset.top; [subView setContentOffset:offset animated:YES]; } [self searchScrollViewInSuperView:subView]; } }
找里面的scrollView时,需要用到递归的思想,一层层往下遍历,而且找到的scrollView也必须是显示在keyWindow上的!所以给UIView分类提供一个方法:- (BOOL)isShowingOnkeyWindow; 用来判断某个UIView在不在主窗口上,以下是实现:
- (BOOL)isShowingOnkeyWindow { // 主窗口 UIWindow *window = [UIApplication sharedApplication].keyWindow; // 以窗口左上角为坐标原地,计算self的矩形框 CGRect newFrame = [window convertRect:self.frame fromView:self.superview]; CGRect windowBounds = window.bounds; // 主窗口的bounds是否 和 self的矩形框有重叠 BOOL isIntersects = CGRectIntersectsRect(newFrame, windowBounds); // 列举self在主窗口的几个必要条件 return !self.hidden && self.window == window && self.alpha > 0.01 && isIntersects; }
判断的时候,必须得转换坐标系,而且得符合几个必须条件:
1、该View不是隐藏的
2、该View是有主窗口的
3、该View是可见的,也就是alpha > 0.01
4、该view和主窗口有重叠
到这里,第一个需求就完成了!
二、点击评论cell时,弹出选项条
如果想实现这个效果,得用到apple自带的一个类(UIMenuController)
以下是该类的具体使用:
1> UIMenuController须知
默认情况下, 有以下控件已经支持UIMenuControllerUITextField
UITextView
UIWebView
2> 让其他控件也支持UIMenuController(比如UILabel)
自定义UILabel重写2个方法
/** * 让label有资格成为第一响应者 */ - (BOOL)canBecomeFirstResponder { return YES; } /** * label能执行哪些操作(比如copy, paste等等) * @return YES:支持这种操作 */ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { if (action == @selector(cut:) || action == @selector(copy:) || action == @selector(paste:)) return YES; return NO; }
实现各种操作方法
- (void)cut:(UIMenuController *)menu { // 将自己的文字复制到粘贴板 [self copy:menu]; // 清空文字 self.text = nil; } - (void)copy:(UIMenuController *)menu { // 将自己的文字复制到粘贴板 UIPasteboard *board = [UIPasteboard generalPasteboard]; board.string = self.text; } - (void)paste:(UIMenuController *)menu { // 将粘贴板的文字 复制 到自己身上 UIPasteboard *board = [UIPasteboard generalPasteboard]; self.text = board.string; }
让label成为第一响应者
// 这里的self是label [self becomeFirstResponder];
显示显示UIMenuController
UIMenuController *menu = [UIMenuController sharedMenuController]; // targetRect: MenuController需要指向的矩形框 // targetView: targetRect会以targetView的左上角为坐标原点 [menu setTargetRect:self.bounds inView:self]; // [menu setTargetRect:self.frame inView:self.superview]; [menu setMenuVisible:YES animated:YES];
3> 自定义UIMenuController内部的Item
添加item// 添加MenuItem(点击item, 默认会调用控制器的方法) UIMenuItem *ding = [[UIMenuItem alloc] initWithTitle:@"顶" action:@selector(ding:)]; UIMenuItem *replay = [[UIMenuItem alloc] initWithTitle:@"回复" action:@selector(replay:)]; UIMenuItem *report = [[UIMenuItem alloc] initWithTitle:@"举报" action:@selector(report:)]; menu.menuItems = @[ding, replay, report];
所以很明显,我们的需求应该就是自定义MenuItem!
接下来在点击cell的方法中弹出选项条
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { UIMenuController *menu = [UIMenuController sharedMenuController]; if (menu.isMenuVisible) { // 如果menu是显示的,再次点击就隐藏 [menu setMenuVisible:NO animated:YES]; return; }else { // 取出cell DSCommentCell *cell = [tableView cellForRowAtIndexPath:indexPath]; // 让cell变成第一响应者 [cell becomeFirstResponder]; // 添加UIMenuItem(点击item,默认会调用控制器的方法) UIMenuItem *ding = [[UIMenuItem alloc] initWithTitle:@"顶" action:@selector(ding:)]; UIMenuItem *replay = [[UIMenuItem alloc] initWithTitle:@"回复" action:@selector(replay:)]; UIMenuItem *report = [[UIMenuItem alloc] initWithTitle:@"举报" action:@selector(report:)]; menu.menuItems = @[ding, replay, report]; CGRect targetRect = CGRectMake(0, cell.height * 0.5, cell.width, cell.height * 0.5); /* targetRect : UIMenuController指向的矩形框 targetView : targetRect会以targetView的左上角为坐标原点 */ [menu setTargetRect:targetRect inView:cell]; [menu setMenuVisible:YES animated:YES]; } }
由于是让cell支持UIMenuController,所以重写的两个方法得写在自定义cell里面!
三、点击tabbar,scrollView会自动回滚到顶部
在很多APP里面,一般都会有点击tabbar后,当前页面会自动刷新!
实现思路:
让AppDelegate成为tabbarController的代理
// 设置根控制器 DSTabBarController *tabbarVc = [[DSTabBarController alloc] init]; tabbarVc.delegate = self; self.window.rootViewController = tabbarVc;
然后实现代理方法- (void)tabBarController:(UITabBarController )tabBarController didSelectViewController:(UIViewController )viewController,在该代理方法中发出通知
#pragma mark - UITabBarControllerDelegate /** * 监听tabbar的选中 */ - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController { [DSNotificationCenter postNotificationName:DSTabBarDidSelectedNotification object:nil userInfo:nil]; }
接着在DSBaseViewController里面监听通知
// 监听tabbar的选中通知 [DSNotificationCenter addObserver:self selector:@selector(tabBarSelect) name:DSTabBarDidSelectedNotification object:nil];
实现监听方法,在监听方法中监听tabbar的选中
- (void)tabBarSelect { // 判断是不是同一次点击,而且点击的控制器view在不在keywindow上 if (self.lastSelectedIndex == self.tabBarController.selectedIndex && self.tableView.isShowingOnkeyWindow) { // 头部开始刷新 [self.tableView.mj_header beginRefreshing]; } // 保存当前选中的index(控制器) self.lastSelectedIndex = self.tabBarController.selectedIndex; }
四、实现新帖控制器内容
由于新帖的展示内容和精华是一模一样的(唯一的不同点就是请求参数的不同),所以考虑用继承来完成!
实现思路:
1> 让DSNewViewController继承自DSEssenceViewController
#import "DSEssenceViewController.h" @interface DSNewViewController : DSEssenceViewController @end
2> 设置请求参数
- (NSString *)a { return [self.parentViewController isKindOfClass:[DSNewViewController class]] ? @"newlist" : @"list"; }
这里得判断父控制器是不是这个类型或者继承自这个类型,所以这个控制器的类型不能是DSEssenceViewController,因为DSNewViewController是继承自DSEssenceViewController的,所以条件也会成立!因此得用DSNewViewController,这个时候就完成了新帖的界面!
接着会出现一个Bug,那就是当底部评论为0的时候,点击评论界面会崩溃,报以下错误:
这个错误很明显,调用了NSArray的objectForKeyedSubscript方法,但是方法找不到!因为objectForKeyedSubscript是字典的方法!而且出错的地方就是评论控制器的loadNewComments方法里面的block!
其实这个错误是由于服务器引起的,因为服务器在数据为nil的时候,返回的是一个数组,而不是字典,这就是导致报错的原因!所以解决办法很简单:
// 说明没有数据 if (![responseObject isKindOfClass:[NSDictionary class]]) { // 结束刷新 [self.tableView.mj_header endRefreshing]; return ; }
五、实现我的界面
该界面主要就是一个tableView(两个cell + 一个自定义的tableFooterView),关于cell就不多说了,主要说一下底部的自定义tableFooterView!
1> 自定义DSMeFooterView,继承自UIView,然后在控制器中设置给tableView的tableFooterView属性
// 设置footer DSMeFooterView *footer = [[DSMeFooterView alloc] init]; self.tableView.tableFooterView = footer;
2> 在自定义DSMeFooterView添加按钮,在内部发送请求,获取里面按钮的图片和文字,以及点击后需要跳转的url
- (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // 请求参数 NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"a"] = @"square"; params[@"c"] = @"topic"; // 发送请求 [[AFHTTPSessionManager manager] GET:@"http://api.budejie.com/api/api_open.php" parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { } success:^(NSURLSessionDataTask * _Nonnull task, NSDictionary * _Nullable responseObject) { // 字典数组 -> 模型数组 NSArray *squares = [DSSquare mj_objectArrayWithKeyValuesArray:responseObject[@"square_list"]]; // 创建按钮 [self createSquares:squares]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { }]; } return self; } - (void)createSquares:(NSArray *)squares { // 方块数量 NSInteger squareCount = squares.count; // 最大列数 NSInteger maxCols = 4; CGFloat buttonW = self.width / maxCols; CGFloat buttonH = buttonW; for (int i = 0; i < squareCount; i++) { // 取出模型 DSSquare *square = squares[i]; DSSquareButton *btn = [[DSSquareButton alloc] init]; btn.square = square; [btn addTarget:self action:@selector(btnClick:) forControlEvents:(UIControlEventTouchUpInside)]; NSInteger col = i % maxCols; NSInteger row = i / maxCols; btn.width = buttonW; btn.height = buttonH; btn.x = buttonW * col; btn.y = buttonH * row; [self addSubview:btn]; // 计算footer高度 // self.height = CGRectGetMaxY(btn.frame); } // 总页数 = (总个数 + 每页最大数 - 1) / 每页最大数 NSInteger rows = (squareCount + maxCols - 1) / maxCols; self.height = rows * buttonH; }
在创建的时候就发送请求获取服务器数据,然后创建按钮。由于按钮的样式和默认是不一样的,所以按钮也需要自定义!首先传递数据模型,然后实现内部的细节处理!
/** * 初始化子控件 */ - (void)setup { self.titleLabel.textAlignment = NSTextAlignmentCenter; self.titleLabel.font = [UIFont systemFontOfSize:10]; [self setBackgroundImage:[UIImage imageNamed:@"mainCellBackground"] forState:(UIControlStateNormal)]; [self setTitleColor:[UIColor blackColor] forState:(UIControlStateNormal)]; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setup]; } return self; } - (void)awakeFromNib { [self setup]; } - (void)layoutSubviews { [super layoutSubviews]; // 设置imageView self.imageView.width = self.width * 0.5; self.imageView.height = self.height * 0.6; self.imageView.x = (self.width - self.imageView.width) * 0.5; self.imageView.y = self.height * 0.15; // 设置titleLabel self.titleLabel.x = 0; self.titleLabel.y = CGRectGetMaxY(self.imageView.frame); self.titleLabel.width = self.width; self.titleLabel.height = self.height - self.titleLabel.y; } - (void)setSquare:(DSSquare *)square { _square = square; // 设置文字和图片 [self setTitle:square.name forState:(UIControlStateNormal)]; [self sd_setImageWithURL:[NSURL URLWithString:square.icon] forState:(UIControlStateNormal)]; }
3> 点击按钮,实现url的跳转
首先得创建一个DSWebViewController,然后添加webView
- (void)btnClick:(DSSquareButton *)button { // 如果前缀不是http if (![button.square.url hasPrefix:@"http"]) return; // 传递数据 DSWebViewController *webVc = [[DSWebViewController alloc] init]; webVc.title = button.square.name; webVc.url = button.square.url; // push网页控制器 UITabBarController *tabBar = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController; UINavigationController *nav = tabBar.selectedViewController; [nav pushViewController:webVc animated:YES]; }
push的时候把title和url都传递过去,想要拿到当前的nav控制器,首先需要拿到tabbarController,然后拿到选中的nav控制器,就可以实现跳转了!
4> 在DSWebViewController的底部添加工具条,用来控制器网页的前进和后退,以及刷新!
以下是控制器代码:
- (void)viewDidLoad { [super viewDidLoad]; self.webView.delegate = self; [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.url]]]; } /** * 上一页 */ - (IBAction)goBack:(id)sender { [self.webView goBack]; } /** * 下一页 */ - (IBAction)goForward:(id)sender { [self.webView goForward]; } /** * 刷新 */ - (IBAction)refresh:(id)sender { [self.webView reload]; } #pragma mark - <UIWebViewDelegate> - (void)webViewDidFinishLoad:(UIWebView *)webView { self.goBackItem.enabled = webView.canGoBack; self.goForward.enabled = webView.canGoForward; }
相关文章推荐
- 集合在foreach时移除数据
- 技术学习路线
- genymotion unable start virtual device
- 图匹配开源网址
- Android SO逆向-流程控制语句及表达式运算
- 游戏引擎架构选摘之第五章 游戏支持系统
- 401 Unauthorized: ERROR Failed to connect to newly launched supervisor. Agent will exit
- IIS配置与错误提示 500.19 - Internal Server Error 无法访问请求的页面,因为该页的相关配置数据无效 解决方法
- AVL树的左右旋转
- VMware Workstation 11 中 Ubuntu 14.04 的 VMware Tools 问题 : 共享文件夹
- hadoop文件的序列化
- Android_UI:drawable文件夹下 创建XML
- Android程序崩溃异常处理之自动发送邮件
- 分布式架构高可用架构篇_04_Keepalived+Nginx实现高可用Web负载均衡
- 多态分类
- Android日志工具Log
- Android开源之BaseRecyclerViewAdapterHelper(持续更新!)
- 几种常见的中文分词包的分析与比较
- [LeetCode]73. Set Matrix Zeroes
- 简单的java使用SAX解析xml