一 传递数据
传递标签数据回到上一个界面(逆传)
方法1
NSMutableArray *tags = [NSMutableArray array]; for (XMGTagButton *tagButton in self.tagButtons) { [tags addObject:tagButton.currentTitle]; }
方法2:KVC
// 将self.tagButtons中存放的所有对象的currentTitle属性值取出来,放到一个新的数组中,并返回
NSArray *tags = [self.tagButtons valueForKeyPath:@"currentTitle"];
!self.getTagsBlock ? : self.getTagsBlock(tags);
// 2.关闭当前控制器
[self dismissViewControllerAnimated:YES completion:nil];
- 取出数据,用block传数据
/** 传递tag数据的block, block的参数是一个字符串数组 */
@property (nonatomic, copy) void (^getTagsBlock)(NSArray *);
XMGAddTagViewController *addTag = [[XMGAddTagViewController alloc] init];
addTag.getTagsBlock = ^(NSArray *tags) {
[weakSelf createTagLabels:tags];
};
控制器A向B发送数据,由A发出通知,控制器A需要保留一个block属性或者delegate
二 显示标签Label
- 创建标签Label
// 所有的标签label
for (int i = 0; i < tags.count; i++) {
// 创建label
UILabel *newTagLabel = [[UILabel alloc] init];
newTagLabel.text = tags[i];
newTagLabel.font = [UIFont systemFontOfSize:14];
newTagLabel.backgroundColor = XMGTagBgColor;
newTagLabel.textColor = [UIColor whiteColor];
newTagLabel.textAlignment = NSTextAlignmentCenter;
[self.topView addSubview:newTagLabel];
[self.tagLabels addObject:newTagLabel];
// 尺寸
[newTagLabel sizeToFit];
newTagLabel.height = XMGTagH;
newTagLabel.width += 2 * XMGCommonSmallMargin;
// 位置
if (i == 0) {
newTagLabel.x = 0;
newTagLabel.y = 0;
} else {
// 上一个标签
UILabel *previousTagLabel = self.tagLabels[i - 1];
CGFloat leftWidth = CGRectGetMaxX(previousTagLabel.frame) + XMGCommonSmallMargin;
CGFloat rightWidth = self.topView.width - leftWidth;
if (rightWidth >= newTagLabel.width) {
newTagLabel.x = leftWidth;
newTagLabel.y = previousTagLabel.y;
} else {
newTagLabel.x = 0;
newTagLabel.y = CGRectGetMaxY(previousTagLabel.frame) + XMGCommonSmallMargin;
}
}
}
- 加号按钮位置
UILabel *lastTagLabel = self.tagLabels.lastObject;
if (lastTagLabel) {
CGFloat leftWidth = CGRectGetMaxX(lastTagLabel.frame) + XMGCommonSmallMargin;
CGFloat rightWidth = self.topView.width - leftWidth;
if (rightWidth >= self.addButton.width) {
self.addButton.x = leftWidth;
self.addButton.y = lastTagLabel.y;
} else {
self.addButton.x = 0;
self.addButton.y = CGRectGetMaxY(lastTagLabel.frame) + XMGCommonSmallMargin;
}
} else {
self.addButton.x = 0;
self.addButton.y = 0;
}
移除所有Label,因为Label是重写创建
方法1: for-in
// 移除所有的label for (UILabel *label in self.tagLabels) { [label removeFromSuperview]; }
方法2:removeFromSuperview方法
// 让self.tagLabels数组中的所有对象执行removeFromSuperview方法
[self.tagLabels makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.tagLabels removeAllObjects];
- 一点加号数据传输下一个view(顺传)
addTag.tags = [self.tagLabels valueForKeyPath:@"text"];
三 工具条
- 计算工具条高度
// 计算工具条的高度
self.topViewHeight.constant = CGRectGetMaxY(self.addButton.frame);
CGFloat oldHeight = self.height;
self.height = self.topViewHeight.constant + self.bottomView.height + XMGCommonSmallMargin;
self.y += oldHeight - self.height; // y增大,旧高度 - 新高度
高度调整最好不要约束和frame一起用,容易出问题
调整高度约束:拖线,定义高度为topViewHeight
- 进来有2个默认标签:“吐槽”“糗事”
- (void)awakeFromNib
{
[self createTagLabels:@[@"吐槽", @"糗事"]];
}
- 当Xib尺寸比较小时,标签布局会有问题:
- 布局代码写到layoutSubviews
- 标签创建完,删除旧标签,所有标签默认都在(0,0)位置
- 没有调用layoutSubviews,标签都叠在第一位
- 需要重新布局子控件: [self setNeedsLayout];
- 如果标签没有文字,点击删除直接return
// 设置点击删除键需要执行的操作
textField.deleteBackwardOperation = ^{
// 判断文本框是否有文字
if (weakSelf.textField.hasText || weakSelf.tagButtons.count == 0) return;
// 点击了最后一个标签按钮(删掉最后一个标签按钮)
[weakSelf tagClick:weakSelf.tagButtons.lastObject];
};
- 删除完标签时,左侧出现间隙,增加判断如下:
XMGTagButton *lastTagButton = self.tagButtons.lastObject;
if (lastTagButton == nil) {
self.textField.x = 0;
self.textField.y = 0;
}
四 通知
- 2种做法实现“控制器内部”通知的注册和移除
做法1:不管当前控制器是否显示在用户眼前,只要控制器对象还在内存中,就会处理各个地方发出的通知
- 1>在viewDidLoad方法中注册通知的监听
- 2>在dealloc方法中移除通知的监听
做法2:只有当前控制器显示在用户眼前,才会处理通知(代码)
- 1> 在viewWillAppear:方法中注册通知的监听
- 2> 在viewWillDisappear:方法中移除通知的监听
- (void)viewWillAppear:(BOOL)animated // 即将显示
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated // 即将消失
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
五 父子控制器
- 场景:控制器挂掉,控制器的view还在,会有坏内存访问
- 子控制器属于局部变量,所以作用域结束,子控制器就挂掉
- 控制器的view添加到self.view上,被强引用,view不会死掉
存在的问题分析
控制器已经死了,但是控制器的view还在
- 1> 点击控制器view内部的按钮、开关,直接EXEC_BAD_ACCESS错误(野指针错误,坏内存访问)
- 2> 拖拽tableView,tableView上面显示的数据突然消失了
叠加了很多不必要的view
- 控制器创建得比较频繁
解决方案
- 保证控制器只创建一次,而且不让它马上死(懒加载,强引用控制器)
- 保证控制器和控制器的view同在
- 点击控制器1,把正在显示的控制器移除掉,添加到即将显示的控制器的view,转为正在显示控制器,其他控制器view移除掉
- 重构代码:
/**
* 切换控制器
* @param index 控制器的索引
*/
- (void)switchToVc:(int)index
{
// 移除当前正在显示的控制器的view
[self.showingVc.view removeFromSuperview];
// 添加即将显示的控制器的view
UIViewController *willShowVc = self.vcs[index];
willShowVc.view.frame = CGRectMake(0, 55, self.view.frame.size.width, self.view.frame.size.height - 55);
[self.view addSubview:willShowVc.view];
// 即将显示 -> 正在显示
self.showingVc = willShowVc;
}
- 重构方法2(严重依赖按钮顺序)
// 对应n个Button连线
- (IBAction)testClick:(UIButton *)button {
// 计算按钮在父控件中的位置
int index = (int)[button.superview.subviews indexOfObject:button];
// 切换控制器
[self switchToVc:index];
}
- (void)switchToVc:(int)index
{
// 移除当前正在显示的控制器的view
[self.showingVc.view removeFromSuperview];
// 添加即将显示的控制器的view
UIViewController *willShowVc = self.vcs[index];
willShowVc.view.frame = CGRectMake(0, 55, self.view.frame.size.width, self.view.frame.size.height - 55);
[self.view addSubview:willShowVc.view];
// 即将显示 -> 正在显示
self.showingVc = willShowVc;
}
- 屏幕旋转
- 监听方法
// 即将旋转
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
// 已经旋转
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
- 屏幕的旋转事件,首先会传递给窗口的根控制器(window.rootViewController);
- 窗口根控制器又会将屏幕的旋转事件,传递给它的子控制器;
- 子控制器又会将屏幕的旋转事件,传递给它自己的子控制器;
- 以此类推,所有的子控制器都能监听到屏幕的旋转事件。
- 目前只有main控制器能监听旋转事件,下列控制器监听不到旋转事件?
- 因为下面控制器只是装在数组里,并不是子控制器
/** 存放所有的控制器 */
@property (nonatomic, strong) NSArray *vcs;
- (NSArray *)vcs
{
if (!_vcs) {
_vcs = @[
[[XMGTest0ViewController alloc] init],
[[XMGTest1ViewController alloc] init],
[[XMGTest2ViewController alloc] init]
];
}
return _vcs;
}
- 所以要想成为子控制器?
// 通过addChildViewController:添加的控制器,就会成为子控制器,会被自动添加到childViewControllers数组中
[self addChildViewController:[[XMGTest0ViewController alloc] init]];
- 屏幕旋转另一方法
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
// 重写这个事件,必须调用super,才能传递给子控制器
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
父子控制器的开发准则:
1. 如果2个控制器的view是父子关系,那么这2个控制器也应该是父子关系
2. 代码表示形式:(包括间接加上去view和控制器)
[vc0.view addSubview:vc1.view];
[vc0 addChildViewController:vc1];
- 父子控制器的重要性-push
- 在Main控制器上添加一个Red控制器, 点击Main控制器跳转到Green控制器
- Red控制器不是子控制器,不能跳转
- Red控制器是子控制器,会顺着父控制器望上级一层层查有没有UINavigationController
- 在Main控制器上添加一个Red控制器, 点击Main控制器跳转到Green控制器
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
GreenViewController *green = [[GreenViewController alloc] init];
green.title = @"点击Red过来的";
[self.navigationController pushViewController:green animated:YES];
// 等价写法:
// [((UINavigationController *)self.parentViewController.parentViewController) pushViewController:green animated:YES];
// 逐级查找
// self.parentViewController == XMGMainViewController
// self.parentViewController.parentViewController == XMGTestViewController
//self.parentViewController.parentViewController.parentViewController == UINavigationController;
}
- 父子控制器的重要性-modal
- 点击White控制器modal出Red控制器,点击Red控制器退出
- 创建Green控制器,添加到Red控制器,需要点击Green控制器退出Red控制器
- 点击Red控制器,dismiss正确的
- 点击Green控制器,如果不是Red控制器,就不能dismiss
- Green控制器检查自己是不是modal出来的,如果不是检查父控制器是不是moadl,以此类推…
- (void)viewDidLoad {
[super viewDidLoad];
XMGGreenViewController *green = [[XMGGreenViewController alloc] init];
green.view.frame = CGRectMake(0, 100, 200, 200);
green.view.autoresizingMask = UIViewAutoresizingNone;
[self.view addSubview:green.view];
[self addChildViewController:green];
}
注意默认情况下,控制器的view.autoresizingMask的值是:
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
父控件宽高变化会拉伸子控件宽高
六 精华板块
- 加一个Scrollview和标签栏,顶部底部可以穿透导航栏
- 上下滚动是tableview,水平滚动是Scrollview
- 设置Scrollview
- (void)setupScrollView
{
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.frame = self.view.bounds;
scrollView.backgroundColor = XMGCommonBgColor;
[self.view addSubview:scrollView];
self.scrollView = scrollView;
}
- 设置标签栏
UIView *titlesView = [[UIView alloc] init];
titlesView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.5];
// 设置透明度方法2: titlesView.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.5];
// 设置透明度方法3: titlesView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5];
titlesView.frame = CGRectMake(0, XMGNavBarMaxY, self.view.width, 35);
[self.view addSubview:titlesView];
self.titlesView = titlesView;
- 标签内部内容实现
// 标签栏内部的标签按钮
NSArray *titles = @[@"全部", @"视频", @"声音", @"图片", @"段子"];
NSUInteger count = titles.count;
CGFloat titleButtonH = titlesView.height;
CGFloat titleButtonW = titlesView.width / count;
for (int i = 0; i < count; i++) {
// 创建
XMGTitleButton *titleButton = [XMGTitleButton buttonWithType:UIButtonTypeCustom];
[titleButton addTarget:self action:@selector(titleClick:) forControlEvents:UIControlEventTouchUpInside];
[titlesView addSubview:titleButton];
[self.titleButtons addObject:titleButton];
// 文字
[titleButton setTitle:titles[i] forState:UIControlStateNormal];
// frame
titleButton.y = 0;
titleButton.height = titleButtonH;
titleButton.width = titleButtonW;
titleButton.x = i * titleButton.width;
}
- 监听点击,颜色可以切换
- 选中状态还可以点选,重复刷新
// 控制按钮状态
self.selectedTitleButton.selected = NO;
titleButton.selected = YES;
self.selectedTitleButton = titleButton;
- 以下方法,可以实现选中过一次不能在点选
self.selectedTitleButton.enabled = YES;
titleButton.enabled = NO;
self.selectedTitleButton = titleButton;
- 取消高亮状态,重写方法
- (void)setHighlighted:(BOOL)highlighted { }
- 标签底部添加高度为1的红色view
// 标签栏底部的指示器控件
UIView *titleBottomView = [[UIView alloc] init];
titleBottomView.backgroundColor = [self.titleButtons.lastObject titleColorForState:UIControlStateSelected];
titleBottomView.height = 1;
titleBottomView.y = titlesView.height - titleBottomView.height;
[titlesView addSubview:titleBottomView];
self.titleBottomView = titleBottomView;
- 默认点击最前面的按钮
[self titleClick:self.titleButtons.firstObject];
注意:Button和Label的宽度,红色view的宽度就是Label的宽度
// 默认点击最前面的按钮
XMGTitleButton *firstTitleButton = self.titleButtons.firstObject;
[firstTitleButton.titleLabel sizeToFit];
titleBottomView.width = firstTitleButton.titleLabel.width;
titleBottomView.centerX = firstTitleButton.centerX;
[self titleClick:firstTitleButton];
注意:当不显示view时候,检查代码顺序,先添加子控制器,才能调用方法
scrollView滚动完毕后,才添加对应子控制器的view到scrollView中
scrollView设置contentsize,才能实现滚动
- 自动调整scrollViewV的滚动条方法
self.automaticallyAdjustsScrollViewInsets = NO; // 不自动调
- 点A标签滚动到对应位置:
CGPoint offset = self.scrollView.contentOffset;
offset.x = self.view.width * [self.titleButtons indexOfObject:titleButton];
[self.scrollView setContentOffset:offset animated:YES];