第一课
颜色常识
颜色:3种颜色通道 R G B
颜色表达方式
24位
32位
每一个颜色通道是8位,0~256
R:213 G:213 B:213
#ffffff
R:FF => 10进制 255
G:FF 255
B:FF 255
#:美工16进制表示形式
0x:OC16进制表达式
BuDeJie项目搭建
优先级:LaunchScreen > LaunchImage
在xcode配置了,不起作用 1.清空xcode缓存 2.直接删掉程序 重新运行
如果是通过LaunchImage设置启动界面,那么屏幕的可视范围由图片决定
注意:如果使用LaunchImage,必须让你的美工提供各种尺寸的启动图片
LaunchScreen:Xcode6开始才有
LaunchScreen好处:1.自动识别当前真机或者模拟器的尺寸 2.只要让美工提供一个可拉伸图片
3.展示更多东西
LaunchScreen底层实现:把LaunchScreen截屏,生成一张图片.作为启动界面
处理tabbar选中标题
1.选中按钮的标题颜色:黑色 标题字体大 -> 对应子控制器的tabBarItem
2.发布按钮显示不出来
*/
// 只会调用一次
+ (void)load
{
// 获取哪个类中UITabBarItem
UITabBarItem *item = [UITabBarItem appearanceWhenContainedIn:self, nil];
// 设置按钮选中标题的颜色:富文本:描述一个文字颜色,字体,阴影,空心,图文混排
// 创建一个描述文本属性的字典
NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
attrs[NSForegroundColorAttributeName] = [UIColor blackColor];
[item setTitleTextAttributes:attrs forState:UIControlStateSelected];
// 设置字体尺寸:只有设置正常状态下,才会有效果
NSMutableDictionary *attrsNor = [NSMutableDictionary dictionary];
attrsNor[NSFontAttributeName] = [UIFont systemFontOfSize:13];
[item setTitleTextAttributes:attrsNor forState:UIControlStateNormal];
}
UIappearance
appearance:只能在控件显示之前设置,才有作用
第二课
利用系统属性实现TabBar
XMGPublishViewController *publishVc = self.childViewControllers[2];
publishVc.tabBarItem.image = [UIImage imageOriginalWithName:@"tabBar_publish_icon"];
publishVc.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_publish_click_icon"];
publishVc.tabBarItem.imageInsets = UIEdgeInsetsMake(7, 0, -7, 0);
tabbar中的子控件tabbutton是在viewwillappear中添加
调整百思TabBarButton
自定tabBar,实现tabBar的layoutSubView
@interface XMGTabBar()
@property (nonatomic, weak) UIButton *plusButton;
@end
@implementation XMGTabBar
- (UIButton *)plusButton
{
if (_plusButton == nil) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:[UIImage imageNamed:@"tabBar_publish_icon"] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:@"tabBar_publish_click_icon"] forState:UIControlStateHighlighted];
[button sizeToFit];
[self addSubview:button];
_plusButton = button;
}
return _plusButton;
}
- (void)layoutSubviews
{
[super layoutSubviews];
NSInteger count = self.items.count;
int i = 0;
CGFloat btnW = self.bounds.size.width / (count + 1);
CGFloat btnH = self.bounds.size.height;
CGFloat x = 0;
for (UIView *tabBarButton in self.subviews) {
if ([tabBarButton isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
if (i == 2) {
i+=1;
}
x = i * btnW;
tabBarButton.frame = CGRectMake(x, 0, btnW, btnH);
i++;
}
}
self.plusButton.center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);
}
设置导航条
UIBarButtonItem:描述按钮具体的内容
UINavigationItem:设置导航条上内容(左边,右边,中间)
tabBarItem: 设置tabBar上按钮内容(tabBarButton)
控件当中有个contentEdgeInsets属性可以用来微调控件的位置
backButton.contentEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0);
全屏滑动返回
// 恢复滑动返回功能 -> 分析:把系统的返回按钮覆盖 -> 1.手势失效(1.手势被清空 2.可能手势代理做了一些事情,导致手势失效)
/*
UIPanGestureRecognizer
UIScreenEdgePanGestureRecognizer:导航滑动手势
target=<_UINavigationInteractiveTransition 0x7fdc4a740440>)
action=handleNavigationTransition:
<UIScreenEdgePanGestureRecognizer: 0x7fdc4a740120; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7fdc4a73e690>; target= <(action=handleNavigationTransition:, >>
*/
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self.interactivePopGestureRecognizer.delegate action:@selector(handleNavigationTransition:)];
[self.view addGestureRecognizer:pan];
// 控制手势什么时候触发,只有非根控制器才需要触发手势
pan.delegate = self;
// 禁止之前手势
self.interactivePopGestureRecognizer.enabled = NO;
实现代理
#pragma mark - UIGestureRecognizerDelegate
// 决定是否触发手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return self.childViewControllers.count > 1;
}
屏幕适配宏
/***********屏幕适配*************/
#define XMGScreenW [UIScreen mainScreen].bounds.size.width
#define XMGScreenH [UIScreen mainScreen].bounds.size.height
#define iphone6P (XMGScreenH == 736)
#define iphone6 (XMGScreenH == 667)
#define iphone5 (XMGScreenH == 568)
#define iphone4 (XMGScreenH == 480)
/***********屏幕适配*************/
cocoapods
经常遇见的困难
[!] Smart quotes were detected and ignored in your Podfile. To avoid issues in the future, you should not use TextEdit for editing it. If you are not using TextEdit, you should turn off smart quotes in your editor of choice.
解决办法:不要使用文本编辑去编辑Podfile,使用Xcode编辑,或者使用终端敲命令去编辑。
作者:丶奔波儿灞
链接:https://www.jianshu.com/p/b32d6c46f153
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
pod更新后之前Podfile写法可能会不能正常使用,可以尝试新的写法,指定工程:
platform :ios, '8.0'
use_frameworks!
target “工程名” do
pod 'Charts', '~> 2.1.6'
end
广告界面实现数据
-(AFHTTPSessionManager *)mgr
{
if (_mgr == nil) {
_mgr = [AFHTTPSessionManager manager];
_mgr.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", nil];
}
return _mgr;
}
第三课
NSTimer使用weak引用
@property (nonatomic, weak) NSTimer *timer;
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
添加东西(控件)的时候,第一件想的事情是要添加几次东西(控件),多数控件只需要添加一次,那么就可以说那个懒加载添加
处理tableView中Cell的分割线占据整个屏幕
- ios8.0以后
在自定义cell中awakefromnib中
self.layoutMargins = UIEdgeInsetsZero;
在tableView中viewdidLoad中
self.tableView.separatorInset = UIEdgeInsetsZero;
- 万能方法
- 万能方式(重写cell的setFrame) 了解tableView底层实现了解 1.取消系统自带分割线 2.把tableView背景色设置为分割线的背景色 3.重写setFrame
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
- (void)setFrame:(CGRect)frame
{
XMGLog(@"%@",NSStringFromCGRect(frame));
frame.size.height -= 1;
// 才是真正去给cell赋值
[super setFrame:frame];
}
使用AFN请求数据时需要考虑网速慢和没网络的情况
[SVProgressHUD showWithStatus:@"正在加载....."];
[SVProgressHUD dismiss];
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[SVProgressHUD dismiss];
[_mgr.tasks makeObjectsPerformSelector:@selector(cancel)];
}
一个view从xib加载,需不需在重新固定尺寸 一定要在重新设置一下
在viewDidLoad设置控件frame好不好,开发中一般在viewDidLayoutSubviews布局子控件
自定义button调整button中子控件的布局
@implementation XMGFastButton
-(void)layoutSubviews
{
[super layoutSubviews];
self.imageView.xmg_y = 0;
self.imageView.xmg_centerX = self.xmg_width *0.5;
self.titleLabel.xmg_y = self.xmg_height - self.titleLabel.xmg_height;
[self.titleLabel sizeToFit];
self.titleLabel.xmg_centerX = self.xmg_width * 0.5;
}
第四课
快速设置占位文字颜色
// 快速设置占位文字颜色 => 文本框占位文字可能是label => 验证占位文字是label => 拿到label => 查看label属性名(1.runtime 2.断点)
// self.placeholderColor = [UIColor redColor];
使用runtime快速设置占位文字颜色
#import "UITextField+PlaceHolder.h"
#import <objc/message.h>
@implementation UITextField (PlaceHolder)
+(void)load
{
Method method1 = class_getInstanceMethod(self, @selector(setPlaceholder:));
Method method2 = class_getInstanceMethod(self, @selector(setXmg_Placeholder:));
method_exchangeImplementations(method1, method2);
}
-(void)setXmg_Placeholder:(NSString *)placeholder
{
[self setXmg_Placeholder:placeholder];
self.placeHolderColor = self.placeHolderColor;
}
-(void)setPlaceHolderColor:(UIColor *)placeHolderColor
{
objc_setAssociatedObject(self, @"placeholderColor", placeHolderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = placeHolderColor;
}
-(UIColor *)placeHolderColor{
return objc_getAssociatedObject(self, @"placeholderColor");
}
在tableView中的footView中添加collectionView
注意:在tableView中tableView的滚动范围是用系统计算的,不要自己去计算不然会出现bug
// 设置tableView滚动范围:自己计算
self.tableView.tableFooterView = self.collectionView;
// self.tableView.contentSize = CGSizeMake(0, CGRectGetMaxY(self.collectionView.frame));
计算collectionView的总行数的万能公式
Rows = (count - 1) / cols + 1
处理cell间距
默认tableView分组样式,有额外头部和尾部间距
self.tableView.sectionHeaderHeight = 0;
self.tableView.sectionFooterHeight = 10;
tableView如果有导航条,contentinset会默认下移64
self.tableView.contentInset = UIEdgeInsetsMake(-25, 0, 0, 0);
跳转界面 push 展示网页
1.Safari openURL :自带很多功能(进度条,刷新,前进,倒退等等功能),必须要跳出当前应用
2.UIWebView (没有功能) ,在当前应用打开网页,并且有safari,自己实现,UIWebView不能实现进度条
3.SFSafariViewController:专门用来展示网页 需求:即想要在当前应用展示网页,又想要safari功能 iOS9才能使用
4.WKWebView
1.导入#import <SafariServices/SafariServices.h>
SFSafariViewController使用Modal
webKit框架使用
需要手动在xcode界面的link frameworks and libraries中导入框架
webkit当中普遍使用键值监听
使用KVO的时候要注意要在delloc中移除监听
获取沙盒文件尺寸
需要NSFileManager对象和attributesOfItemAtPath方法
attributesOfItemAtPath只能获取文件尺寸,获取文件夹不对
完善文件管理的工具类
注意在计算文件尺寸的时候要开异步线程计算,因为异步所以不能有返回值,需要把计算结果回调给方法调用者,回调函数要先回到主线程
方法调用者可以在进入控制器的viewdidload中开始计算尺寸,把缓存保存成属性
删除缓存的时候记得清空缓存属性清空
第五课
透明度
父控件的透明度会被子控件继承
button的setHighlighted方法
重写这个方法,按钮就无法进入highlighted状态
用构造代码创建Button是需要注意
会调用initWithFrame方法
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//可以在这个代码块中设置按钮的字体样式(包括字体颜色和字体大小)
}
return self;
}
按钮的状态
1.UIControlStateNormal
1> 除开UIControlStateHighlighted、UIControlStateDisabled、UIControlStateSelected以外的其他情况,都是normal状态
2> 这种状态下的按钮【可以】接收点击事件
2.UIControlStateHighlighted
1> 【当按住按钮不松开】或者【highlighted = YES】时就能达到这种状态
2> 这种状态下的按钮【可以】接收点击事件
3.UIControlStateDisabled
1> 【button.enabled = NO】时就能达到这种状态
2> 这种状态下的按钮【无法】接收点击事件
4.UIControlStateSelected
1> 【button.selected = YES】时就能达到这种状态
2> 这种状态下的按钮【可以】接收点击事件
二、让按钮无法点击的2种方法
1> button.enabled = NO;
*【会】进入UIControlStateDisabled状态
2> button.userInteractionEnabled = NO;
*【不会】进入UIControlStateDisabled状态,继续保持当前状态
tableView的重要属性
[图片上传失败...(image-f27ae4-1520299564234)]
[图片上传失败...(image-cac32a-1520299564234)]
[图片上传失败...(image-b853c9-1520299564234)]
第六课
在百思项目中重复点击tabBarButton按钮刷新界面
- 首先在TabbarButton中添加按钮监听
- 在监听中发通知,在每个需要刷新的界面监听通知
- 实现监听方法
- (void)tabBarButtonDidRepeatClick
{
// 重复点击的不是精华按钮
if (self.view.window == nil) return;
// 显示在正中间的不是AllViewController
if (self.tableView.scrollsToTop == NO) return;
XMGLog(@"%@ - 刷新数据", self.class)
}
在百思项目中点击App最顶端状态栏实现app所在界面滑动到顶部
scroll有个属性scrollsToTop
// 设置index位置对应的tableView.scrollsToTop = YES, 其他都设置为NO
for (NSUInteger i = 0; i < self.childViewControllers.count; i++) {
UIViewController *childVc = self.childViewControllers[i];
// 如果view还没有被创建,就不用去处理
if (!childVc.isViewLoaded) continue;
UIScrollView *scrollView = (UIScrollView *)childVc.view;
if (![scrollView isKindOfClass:[UIScrollView class]]) continue;
// if (i == index) { // 是标题按钮对应的子控制器
// scrollView.scrollsToTop = YES;
// } else {
// scrollView.scrollsToTop = NO;
// }
scrollView.scrollsToTop = (i == index);
}
在百思[精华]控制器中,是现在懒加载所有帖子的tabbleView的子控制器
点击了相应的贴子按钮再添加
可以用childVc.isViewLoaded和childVcView.superview和childVcView.window方法来判断view是否被加载过
if (childVc.isViewLoaded) return;
// 取出index位置对应的子控制器view
UIView *childVcView = childVc.view;
if (childVcView.superview) return;
if (childVcView.window) return;
/**
* 添加第index个子控制器的view到scrollView中
*/
- (void)addChildVcViewIntoScrollView:(NSUInteger)index
{
UIViewController *childVc = self.childViewControllers[index];
// 如果view已经被加载过,就直接返回
if (childVc.isViewLoaded) return;
// 取出index位置对应的子控制器view
UIView *childVcView = childVc.view;
// 设置子控制器view的frame
CGFloat scrollViewW = self.scrollView.xmg_width;
childVcView.frame = CGRectMake(index * scrollViewW, 0, scrollViewW, self.scrollView.xmg_height);
// 添加子控制器的view到scrollView中
[self.scrollView addSubview:childVcView];
}
再百思项目中viewwithTag中的注意点
因为viewwithTag是递归函数,所以传进来的索引会从本身函数调用者本身开始调用,又因为tag默认值索引值是0,所以传进来的所以要加上一个较大值(盐),防止从0开始便利系统崩溃
uiview递归方法伪实现
- (UIView *)viewWithTag:(NSInteger)tag
{
// 如果自己的tag符合要求,就返回自己
if (self.tag == tag) return self;
// 遍历子控件(也包括子控件的子控件...),直到找到符合条件的子控件为止
for (UIView *subview in self.subviews) {
// if (subview.tag == tag) return subview;
UIView *resultView = [subview viewWithTag:tag];
if (resultView) return resultView;
}
return nil;
}
项目经验如何从系统报错中快速锁定报错项
-[UIView setSelected:]: unrecognized selector sent to instance 0x7fbcba35ab10
setSelected是uicontrol的方法,报错原因是因为错把uicontrol当做uiview使用
-[XMGPerson length]: unrecognized selector sent to instance 0x7fbcba35ab10
将XMGPerson当做NSString来使用
-[XMGPerson count]: unrecognized selector sent to instance 0x7fbcba35ab10
将XMGPerson当做NSArray或者NSDictionary来使用
-[XMGPerson setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x7fbcba35ab10
名字中带有Subscript的方法,一般都是集合的方法,比如NSMutableDictionary\NSMutableArray的方法
将XMGPersonNSMutableDictionary来使用
百思项目经验
如果要监听某个控件的点击,可以尝试成为这个控件的父控件的代理,如果父控件已经有代理了,可以成为该控件的父控件的父控件的代理,直到找到UIapplication,成为uiappLication的代理
比如点击应用最上方的状态栏
XMGTabBarController *tabBarVc = [[XMGTabBarController alloc] init];
// tabBarVc.delegate = (id<UITabBarControllerDelegate>)[UIApplication sharedApplication].delegate;
第七课
上拉刷新和下拉刷新和封装思路
- 上拉加载更多 判断tableview 滑动的偏移量,需要等到下拉刷新的tableFootView完全出现 再开始刷新(封装),加载更多数据(封装),停止刷新(封装)
- 下拉加载更多,首先tableHeadView要预留做项目拓展功能,所以只能给tableView上添加一个View,下拉刷新要在scrollViewDidEndDragging的代理方法中监听手指放开屏幕是开始刷新(封装),加载最新数据(封装),停止刷新(封装)
自定义把json数据写成plist的宏
// #filename -> "filename"
// @#filename -> @"filename"
#define XMGAFNWriteToPlist(filename) [responseObject writeToFile:[NSString stringWithFormat:@"/Users/chenhengjun/Desktop/%@.plist", @#filename] atomically:YES];
数组两个方法补充
// self.topics = @[10, 9, 8]
// moreTopics = @[7, 6 ,5]
// self.topics = @[10, 9, 8, @[7, 6 ,5]]
[self.topics addObject:moreTopics];
// self.topics = @[10, 9, 8, 7, 6 ,5]
[self.topics addObjectsFromArray:moreTopics];
第八课
上拉加载更多和下拉加载最新数据补充
在开发项目中上拉加载更多数据一般采用累加的模式,所以在请求更多数据的时候经常携带上一页的最后一条数据的id
下拉加载最新数据的时候可以采用两种方法,分别是加载最新的数据覆盖所有数据和之加载数据库当中最新的数据,已经加载的数据不覆盖,不更新.两种方法各有优劣,第一种方法不用携带上一页的第一条数据的id,第二种方法需要携带上一页第一条数据的id
上拉加载更多和下拉加载最新数据共存问题
情况模拟
/*
服务器数据:45,44,43,42,41,40,39,38,37,36,35,34,。。。。。。5,4,3,2,1
下⬇️拉刷新(new-最新)@[45,44,43]
上⬆️拉刷新(more-更多)@[37,36,35]
客户端数据:
self.topics = @[40,39,38]
请求的回来先后顺序
1.上⬆️拉刷新(more-更多)-> 下⬇️拉刷新(new-最新)
self.topics = @[45,44,43]
2.下⬇️拉刷新(new-最新)-> 上⬆️拉刷新(more-更多)
self.topics = @[45,44,43,37,36,35]
*/
解决方法:1:可以在上拉加载的时候判断如果正在下拉加载更多,如果是,那么直接返回不执行接下去的代码,在下拉加载更多的时候判断是否在上拉加载,如果是,那么直接返回不执行接下去的代码
- (void)headerBeginRefreshing
{
// // 如果正在上拉刷新,直接返回
// if (self.isFooterRefreshing) return;
解决方法:2可以在发送上拉加载的时候取消下拉加载请求,在发送下拉加载的时候取消上拉加载请求
如果使用第二种方法必须注意,如果取消请求afn默认会调用failure,需要在failure中判断取消请求的情况
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (error.code != NSURLErrorCancelled) { // 并非是取消任务导致的error,其他网络问题导致的error
[SVProgressHUD showErrorWithStatus:@"网络繁忙,请稍后再试!"];
}
在百思项目的精华模块中,需要自定义cell
第一种是使用纯代码,把四种不同样式cell独立出来,工作量大
第二种实现思路:因为四种cell的头部和尾部相同,只有中间部分不同,所以我们只需要自定义一种cell,cell用xib描述,中间部分可以使用占位视图,设置中间部分占位视图的约束优先级,中间部分有内容时显示,没有内容时隐藏
定义枚举复习
typedef NS_ENUM(NSUInteger, XMGTopicType) {
/** 全部 */
XMGTopicTypeAll = 1,
/** 图片 */
XMGTopicTypePicture = 10,
/** 段子 */
XMGTopicTypeWord = 29,
/** 声音 */
XMGTopicTypeVoice = 31,
/** 视频 */
XMGTopicTypeVideo = 41
};
返回一张不被拉伸的受保护的图片
UIImage *resizableImage = [image resizableImageWithCapInsets:UIEdgeInsetsMake(imageHeight * 0.5, imageWidth * 0.5, imageHeight * 0.5 -1, imageWidth * 0.5 - 1)];
//另外的一个方法
// UIImage *resizableImage = [image stretchableImageWithLeftCapWidth:imageWidth * 0.5 topCapHeight:imageHeight * 0.5];
tableView中cell和cell之间的间距
重写cell的setFrame方法可以实现
-(void)setFrame:(CGRect)frame
{
frame.origin.x += 10;
frame.size.width -= 20;
frame.size.height -= 10;
[super setFrame:frame];
}
第九课
自定义cell的高度
因为项目中中间部分不固定,动态添加空间,所以cell得高度不固定所有必须自定义cell的高度
实现tableview的代理方法tableView heightForRowAtIndexPath
在这个方法中计算每个cell的height
- heightForRowAtIndexPath这个方法的特点
1.默认情况下
1> 每次刷新表格时,有多少数据,这个方法就一次性调用多少次(比如有100条数据,每次reloadData时,这个方法就会一次性调用100次)
2> 每当有cell进入屏幕范围内,就会调用一次这个方法
计算文本框中文字的高度
需要两个已知参数首先第一个是文本框中字体的大小,第二个是文本框的宽度
可以使用一下过期方法
[UIFont systemFontOfSize:15] constrainedToSize:textMaxSize]
缓存cell的高度
tableView heightForRowAtIndexPath这个方法调用太频繁,所以可以考虑使用字典的属性缓存cell的高度
/** 用来缓存cell的高度(key:模型,value:cell的高度) */
@property (nonatomic, strong) NSMutableDictionary *cellHeightDict;
//为了保存KEY的唯一性,可以使用模型的地址来做字典的key
也可以在模型属性中添加一个cellheight属性来保存cellheight这个值(本项目中采用此类方法)
在模型的.m文件中重写cellheight的get方法
- (CGFloat)cellHeight
{
// 如果已经计算过,就直接返回
if (_cellHeight) return _cellHeight;
XMGFunc
// 文字的Y值
_cellHeight += 55;
// 文字的高度
CGSize textMaxSize = CGSizeMake(XMGScreenW - 2 * XMGMarin, MAXFLOAT);
_cellHeight += [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size.height + XMGMarin;
// 工具条
_cellHeight += 35 + XMGMarin;
return _cellHeight;
}
估算高度estimatedRowHeight
使用estimatedRowHeight的优缺点
1.优点
1> 可以降低tableView:heightForRowAtIndexPath:方法的调用频率
2> 将【计算cell高度的操作】延迟执行了(相当于cell高度的计算是懒加载的)
2.缺点
1> 滚动条长度不准确、不稳定,甚至有卡顿效果(如果不使用estimatedRowHeight,滚动条的长度就是准确的)
*/
/**
这个方法的特点:
1.默认情况下(没有设置estimatedRowHeight的情况下)
1> 每次刷新表格时,有多少数据,这个方法就一次性调用多少次(比如有100条数据,每次reloadData时,这个方法就会一次性调用100次)
2> 每当有cell进入屏幕范围内,就会调用一次这个方法
2.设置estimatedRowHeight的情况下
1> 用到了(显示了)哪个cell,才会调用这个方法计算那个cell的高度(方法调用频率降低了)
最热评论
注意:在xib中不用给cmt_view设置高度,需要让从cmt_view中的内容子控件的底部等于cmt_view的底部,那么cmt_view会随着内容的增大缩小而变化
最热评论可能是语音评论所以要判断content.length是否为0
if (content.length == 0) {
content = @"[语音评论]";
}
添加中间内容
首先,中间内容是根据type内容决定再动态加载的,所以依次创建3个不同类型的view和xib,这边新增加了一个根据xib加载view的方法xmg_viewFrom方法,然后在topcell类中导入这三个类,懒加载创建这个view,在setTopic方法中判断哪个view需要显示和隐藏.然后在layoutsubview方法中布置子控件
注意:因为,中间的控件多长多宽是由模型中间宽高决定的,所以我们在模型的-setcellHeight中计算middeview的frame,并把值保存到模型的middleframe中
而在layoutsubview中直接拿模型中的middleframe赋值
错误信息补充
如果错误信息里面包含了:NaN,一般都是因为除0造成(比如x/0)
(NaN : Not a number)
中间控件加载大图,中图或小图的判断
- 1.判断大图是否下载过大图
- 2.判断是够是wifi环境
- 2.1判断是否是手机自带网络
- 2.1.1判断偏好设置中是否设定了在手机自带网络中也下载大图
- 2.2判断如果没有可用网络,去沙盒中查找小图是否下载过,如果连小图都没有下载过,那么现实占位图片
// 占位图片
UIImage *placeholder = nil;
// 根据网络状态来加载图片
AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
// 获得原图(SDWebImage的图片缓存是用图片的url字符串作为key)
UIImage *originImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:topic.image1];
if (originImage) { // 原图已经被下载过
self.imageView.image = originImage;
} else { // 原图并未下载过
if (mgr.isReachableViaWiFi) {
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.image1] placeholderImage:placeholder];
} else if (mgr.isReachableViaWWAN) {
// 3G\4G网络下时候要下载原图
BOOL downloadOriginImageWhen3GOr4G = YES;
if (downloadOriginImageWhen3GOr4G) {
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.image1] placeholderImage:placeholder];
} else {
[self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.image0] placeholderImage:placeholder];
}
} else { // 没有可用网络
UIImage *thumbnailImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:topic.image0];
if (thumbnailImage) { // 缩略图已经被下载过
self.imageView.image = thumbnailImage;
} else { // 没有下载过任何图片
// 占位图片;
self.imageView.image = placeholder;
}
}
}
注意:如果判断手机自带网络的时候使用afnetworking框架里的 AFNetworkReachabilityManager类是,记得在uiapplication中开启网络的监听
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
stringwithform补充
self.voicetimeLabel.text = [NSString stringWithFormat:@"%02zd:%02zd", topic.voicetime / 60, topic.voicetime % 60];
//02zd:%02zd 占据两位,多余的空位用0填补
autoresizingMask的补充
一般情况下,以下这些view的autoresizingMask默认就是18(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth)
1.从xib里面创建出来的默认控件
2.控制器的view
如果不希望控件拥有autoresizingMask的自动伸缩功能,应该设置为none
blueView.autoresizingMask = UIViewAutoresizingNone;
第十课
xib的复习
[[XMGVideoViewController alloc] init]
1.XMGVideoViewController.xib
2.XMGVideoView.xib
报错信息:-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "XMGVideoView" nib but the view outlet was not set.
错误原因:在使用xib创建控制器view时,并没有通过File's Owner设置控制器的view属性
解决方案:通过File's Owner设置控制器的view属性为某一个view
报错信息:-[UITableViewController loadView] loaded the "XMGVideoView" nib but didn't get a UITableView.
错误原因:在使用xib创建UITableViewController的view时,并没有设置控制器的view为一个UITableView
解决方案:通过File's Owner设置控制器的view属性为一个UITableView
*/
封装imageView的download方法
- (void)xmg_setOriginImage:(NSString *)originImageURL thumbnailImage:(NSString *)thumbnailImageURL placeholder:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
- (void)xmg_setHeader:(NSString *)headerUrl;
button按钮补充
button控件内部自控件的布局可以使用
// 控件按钮内部子控件之间的间距
// btn.contentEdgeInsets = UIEdgeInsetsMake(10, 0, 0, 0);
// btn.titleEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
// btn.imageEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
第十一课
图片缓存问题
使用SDWebImage框架,可以使用SDImageCache这个类来清楚缓存
在本项目中我们在
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//清楚缓存
[[SDImageCache sharedImageCache] clearDisk];
}
```[[SDImageCache sharedImageCache] cleanDisk];这个方法是清楚沙盒中过期图片,过期天使默认是一个星期,这个方法可以在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中使用
注意:
##大图下载补充
在上一届课中我们是显示完图片以后再判断是否是大图,这样判断不合理
在这一节中我们把判断大图的代码移到下载图片的功能当中
```objc
[self.imageView xmg_setOriginImage:self.topic.image1 thumbnailImage:self.topic.image0 placeholder:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (!image) return ;
self.placeholderView.hidden = YES;
if (topic.isBigPicture) {
CGFloat imageW = topic.middleFrame.size.width;
CGFloat imageH = imageW * topic.height / topic.width;
UIGraphicsBeginImageContext(CGSizeMake(imageW, imageH));
[self.imageView.image drawInRect:CGRectMake(0, 0, imageW, imageH)];
self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}];
在父控件中添加子控件的view(特别是从xib加载子控件)
因为如果是从xib加载的控件,控件的真实尺寸是xib中控件的尺寸,那么在viewdidload方法中添加控件设置控件的尺寸是不准的
开发思路
1.在viewDidLoad方法中添加初始化子控件
2.在viewDidLayoutSubviews方法中布局子控件(设置子控件的位置和尺寸)
另一种常见的开发思路
1.控件弄成懒加载
2.在viewDidLayoutSubviews方法中布局子控件(设置子控件的位置和尺寸)
保存图片到相册
一、保存图片到【自定义相册】
1.保存图片到【相机胶卷】
1> C语言函数UIImageWriteToSavedPhotosAlbum
2> AssetsLibrary框架
3> Photos框架
2.拥有一个【自定义相册】
1> AssetsLibrary框架
2> Photos框架
3.添加刚才保存的图片到【自定义相册】
1> AssetsLibrary框架
2> Photos框架
// 异步执行修改操作
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (error) {
[SVProgressHUD showErrorWithStatus:@"保存失败!"];
} else {
[SVProgressHUD showSuccessWithStatus:@"保存成功!"];
}
}];
// 同步执行修改操作
NSError *error = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
[PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image];
} error:&error];
if (error) {
[SVProgressHUD showErrorWithStatus:@"保存失败!"];
} else {
[SVProgressHUD showSuccessWithStatus:@"保存成功!"];
}
错误信息补充
错误信息:-[NSInvocation setArgument:atIndex:]: index (2) out of bounds [-1, 1]
错误解释:参数越界错误,方法的参数个数和实际传递的参数个数不一致
photo框架须知
-
1.PHAsset : 一个PHAsset对象就代表相册中的一张图片或者一个视频
- 1> 查 : [PHAsset fetchAssets...]
- 2> 增删改 : PHAssetChangeRequest(包括图片\视频相关的所有改动操作)
-
2.PHAssetCollection : 一个PHAssetCollection对象就代表一个相册
- 1> 查 : [PHAssetCollection fetchAssetCollections...]
- 2> 增删改 : PHAssetCollectionChangeRequest(包括相册相关的所有改动操作)
-
3.对相片\相册的任何【增删改】操作,都必须放到以下方法的block中执行
- -[PHPhotoLibrary performChanges:completionHandler:]
- -[PHPhotoLibrary performChangesAndWait:error:]
Foundation和Core Foundation的数据类型可以互相转换,比如NSString *和CFStringRef
NSString *string = (__bridge NSString *)kCFBundleNameKey;
CFStringRef string = (__bridge CFStringRef)@"test";
保存相片到自定义相册代码总实现(包括权限判断)
这么恶心的代码写一次就够了
- (IBAction)save {
PHAuthorizationStatus oldStatus = [PHPhotoLibrary authorizationStatus];
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_sync(dispatch_get_main_queue(), ^{
if (status == PHAuthorizationStatusDenied) {
if (oldStatus != PHAuthorizationStatusNotDetermined) {
NSLog(@"提醒用户打开开关");
}
}else if (status == PHAuthorizationStatusAuthorized)
{
[self saveImageIntoAlbum];
}else if(status == PHAuthorizationStatusRestricted)
{
[SVProgressHUD showErrorWithStatus:@"因系统原因,无法访问相册!"];
}
});
}];
}
#pragma mark - 把相片保存到对应的叫项目名称的照片文件夹中
-(void)saveImageIntoAlbum
{
PHFetchResult *assets = [self createdAssets];
if (assets == nil) {
[SVProgressHUD showErrorWithStatus:@"图片保存失败!"];
return;
}
PHAssetCollection *collcetion = [self createdCollection];
if (collcetion == nil) {
[SVProgressHUD showErrorWithStatus:@"创建或获取相册失败!"];
return;
}
NSError *error = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
PHAssetCollectionChangeRequest *request = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:collcetion];
[request insertAssets:assets atIndexes:[NSIndexSet indexSetWithIndex:0]];
} error:&error];
if (error) {
[SVProgressHUD showErrorWithStatus:@"保存图片失败!"];
} else {
[SVProgressHUD showSuccessWithStatus:@"保存图片成功!"];
}
}
#pragma mark - 获得当前App对应的自定义相册
-(PHAssetCollection *)createdCollection
{
NSString *title = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey];
PHFetchResult<PHAssetCollection *> *collections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
for (PHAssetCollection * collection in collections) {
if ([collection.localizedTitle isEqualToString:title]) {
return collection;
}
}
NSError *error = nil;
__block NSString * createdCollectionID = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
createdCollectionID = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title].placeholderForCreatedAssetCollection.localIdentifier;
} error:&error];
if (error) {
return nil;
}
return [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[createdCollectionID] options:nil].firstObject;
}
#pragma mark - 保存相片到相机胶卷
- (PHFetchResult <PHAsset *>*)createdAssets
{
NSError *error = nil;
__block NSString * assetID = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
assetID = [PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image].placeholderForCreatedAsset.localIdentifier;
} error:&error];
if (error) {
return nil;
}
return [PHAsset fetchAssetsWithLocalIdentifiers:@[assetID] options:nil];
}
ios10新特性补充
ios10新增加了权限的保护,如果要访问手机的相机要在app中的info.plist中设置privacy - photo library usage description这个属性
除了相册的权限,iOS10之后如下的权限请求也是需要我们填写请求描述的,在这里也给大家提醒一下:
Privacy - Microphone Usage Description //麦克风权限
Privacy - Contacts Usage Description //通讯录权限
Privacy - Camera Usage Description //摄像头权限
Privacy - NSSiriUsageDescription //Siri的权限
Privacy - Bluetooth Peripheral Usage Description //蓝牙
Privacy - Reminders Usage Description //提醒事项
Privacy - Motion Usage Description //运动与健康
Privacy - Media Libaray Usage Description //媒体资源库
Privacy - Calendars Usage Description //日历
uiscrollView代理方法viewForZoomingInScrollView:(UIScrollView *)scrollView的补充
在urscroll中可以放大子控件的frame,比如说imageview,,如果需要实现上面功能,只需要设置代理并实现上面方法即可
复习weak和assgin的区别
1.weak
1> OC对象
2.assign
1> 基本数据类型
2> OC对象
3.strong
1> OC对象
4.copy
1> NSString *
2> block
5.使用weak和assign修饰OC对象的区别
1> 成员变量
1) weak生成的成员变量是用__weak修饰的,比如XMGCat * __weak _cat;
2) assign生成的成员变量是用__unsafe_unretained修饰的XMGCat * __unsafe_unretained _cat;
2> __weak和__unsafe_unretained
1) 都不是强指针(不是强引用),不能保住对象的命
2) __weak : 所指向的对象销毁后,会自动变成nil指针(空指针),不再指向已经销毁的对象
3) __unsafe_unretained : 所指向的对象销毁后,仍旧指向已经销毁的对象
错误解析
[图片上传失败...(image-b28fe1-1520299564234)]
修改info.plist文件以后可能会弹框
解决方法:第一种可以整个项目都clean一下,
第二种可以修改整个项目的项目名称
第十二课
从相册中选择单张(多张)相片
一、从相册里面选择图片到App中
1.选择单张图片
1> UIImagePickerController (自带选择界面)
2> AssetsLibrary框架 (选择界面需要开发者自己搭建)
3> Photos框架 (选择界面需要开发者自己搭建)
2.选择多张图片(图片数量 >= 2)
1> AssetsLibrary框架 (选择界面需要开发者自己搭建)
2> Photos框架 (选择界面需要开发者自己搭建)
二、利用照相机拍一张照片到App
1> UIImagePickerController (自带选择界面)
2> AVCapture****,比如AVCaptureSeession
复习UIalertcontrol
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"请选择方式" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
// 按钮
[alert addAction:[UIAlertAction actionWithTitle:@"相册" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self openAlbum];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"照相机" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self openCamera];
}]];
使用pods集成第三方框架时出现项目中pods中项目变红色
这个位正常现象,因为这个第三方框架的作者没有把这个框架依赖的其他框架上传的这个框架中,我们只需要重写安装一下这个第三方框架的依赖
选择多张的相片可以参考CTAssetsPickercontroller这个框架
CTAssetsPickercontroller的简单使用
使用前提是先发送请求权限
在请求权限的block方法中回到主线程
弹出图片选择界面CTAssetsPickerController *picker = [[CTAssetsPickerController alloc] init];
隐藏空相册 picker.showsEmptyAlbums = NO;
显示图片索引picker.showsSelectionIndex = YES;
显示自定义相册,和系统的相机胶卷
picker.assetCollectionSubtypes = @[
@(PHAssetCollectionSubtypeSmartAlbumUserLibrary),
@(PHAssetCollectionSubtypeAlbumRegular)
];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { // 如果当前运行的设备是平板电脑
picker.modalPresentationStyle = UIModalPresentationFormSheet;
}
//formsheet是ipad的特有模式,iphone没有这种样式
modal出控制器
如果需要设置maxcount数量的图片
需要设置这个控制器的代理,然后实现控制器的assetsPickerController:(CTAssetsPickerController *)picker shouldSelectAsset:(PHAsset *)asset代理方法
在这个方法中判断picker.selectedAssets.count的数量,如果数量大于你设置的最多图片,返回no,反之返回yes
选择完毕的使用实现代理方法assetsPickerController:(CTAssetsPickerController *)picker didFinishPickingAssets:(NSArray *)assets
具体实现
- (void)assetsPickerController:(CTAssetsPickerController *)picker didFinishPickingAssets:(NSArray *)assets
{
// 关闭图片选择界面
[picker dismissViewControllerAnimated:YES completion:nil];
// 选择图片时的配置项
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.resizeMode = PHImageRequestOptionsResizeModeExact;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
// 显示图片
for (NSInteger i = 0; i < assets.count; i++) {
PHAsset *asset = assets[i];
CGSize size = CGSizeMake(asset.pixelWidth / [UIScreen mainScreen].scale, asset.pixelHeight / [UIScreen mainScreen].scale);
// 请求图片
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeDefault options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
// 添加图片控件
UIImageView *imageView = [[UIImageView alloc] init];
imageView.image = result;
[self.view addSubview:imageView];
imageView.frame = CGRectMake((i % 3) * (100 + 10), (i / 3) * (100 + 10), 100, 100);
}];
}
}
多语言支持
i18n:国际化(其他语言)
localization:本地化(iOS)
首先
[图片上传失败...(image-dcb017-1520299564234)]-
其次
- 创建resource分类中strings file文件,在stringsfile文件的右侧边属性栏中选择localization需要的语言
-
最后
- 如果项目中使用到本地化,可以这用NSLocalizedStringFromTable和NSLocalizedString这两个宏,这两个宏一个有指定table,一个使用的默认的localizebale.string
开发经验
如果需要给项目中的某个文件添加本地化可以在开发开发过程中设置本地化的内容
[self.button setTitle:NSLocalizedStringFromTable(@"Cancel", @"Test", @"A title on cancel button") forState:UIControlStateNormal];
NSString *string = NSLocalizedStringFromTable(@"Done", @"Test", @"A text on done label");
NSString *string2 = NSLocalizedString(@"Go", @"Run run run");
//cmt的注释需要写详细,方便翻译
然后再使用编译器指令genstrings自动生成strings文件
注意:如果项目名称也需要实现本地化,那么需要创建创建一个单一(专门)的文件InfoPlist.strings文件在这个文件中给KEY位"CFBundleName"的赋值
完善图片下载的bug问题(因为tableview的循环利用导致)
bug原因:中间的内容的图片是发送请求到服务器下载,可能会因为前一个图片还没下载完全,循环利用以后覆盖本来该显示在cell的图片,所以为了解决这个bug,以后如果是下载图片,并且使用SDWebImage进行下载,那么不要直接用self.imageView.image进行赋值应该使用[self sd_setImageWithURL:[NSURL URLWithString:originImageURL] placeholderImage:placeholder completed:completedBlock];这个方法,因为这个方法中有取消上一个请求的功能
精华控制器的最后封装
情境:精华控制器总共有4种类型的自控制器,并且这四种类型的子控制器出中间内容不一样以后,其他部分高度相似,所以我们可以给这四种子控制器抽出一个公共的父类,并在这个公共的父类的.h文件中声明
-(XMGTopicType)type;
声明的方法由子类继承父类,再实现,这样可以保证父类的高度封装性
补充:以上方法为最优方案,还有其他另外的方法如下
- 第一种:
可以在父类中实现-(XMGTopicType)type;的方法,并判断真正调用这个方法的子类类类型,来决定中间控件的数据
- (XMGTopicType)type
{
if ([self isKindOfClass:[XMGAllViewController class]]) return XMGTopicTypeAll;
if ([self isKindOfClass:[XMGVideoViewController class]]) return XMGTopicTypeVideo;
if ([self isKindOfClass:[XMGVoiceViewController class]]) return XMGTopicTypeVoice;
if ([self isKindOfClass:[XMGPictureViewController class]]) return XMGTopicTypePicture;
if ([self isKindOfClass:[XMGWordViewController class]]) return XMGTopicTypeWord;
if (self.class == [XMGAllViewController class]) return XMGTopicTypeAll;
if ([NSStringFromClass(self.class) isEqualToString:@"XMGAllViewController"]) return XMGTopicTypeAll;
return 0;
}
这样做首先必须导入子类,如此子类依赖父类,父类依赖子类,破坏编程逻辑,破坏封装性,并且容易导致循环引用
第二种:在父类中设置type属性,然后在添加控件的时候设置type属性的值,这样导致type属性暴露在外面,任何类都可以给type属性赋值,破坏了封装性
第三种:在父类中设置readonly属性的type值,然后在子类中重写type的get方法给type赋值,但是这样父类中还是生成了_type的属性,并且父类.m文件中生成了gettype的实现,我们依然可以使用KVO给_type属性赋值,破坏了封装性
podfile文件格式
platform :ios, '8.0'
target ‘BuDeJie’ do
pod 'AFNetworking', '~> 3.0.4'
pod 'MJExtension', '~> 3.0.10'
pod 'SDWebImage', '~> 3.7.5'
pod 'SVProgressHUD', '~> 2.0-beta8'
pod 'MJRefresh', '~> 3.1.15.2'
end
第十三课
MJRefresh的基本使用
MJRefresh这个框架继承了所有上拉和下拉刷新,在我们项目中我们使用scrolloffsetY来判定是否刷新,二MJRefresh使用了KVO为实现原理
//header
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)];
self.tableView.mj_header.automaticallyChangeAlpha = YES;
[self.tableView.mj_header beginRefreshing];
//footer
self.tableView.mj_footer = [MJRefreshAutoFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)];
我们在使用这个框架的时候可以使用子类化这个框架的父类,自定义这个控件
#import <MJRefresh/MJRefresh.h>
@interface XMGRefreshHeader : MJRefreshNormalHeader
@end
#import "XMGRefreshHeader.h"
@implementation XMGRefreshHeader
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
// 设置状态文字颜色
self.stateLabel.textColor = [UIColor blueColor];
self.stateLabel.font = [UIFont systemFontOfSize:17];
[self setTitle:@"赶紧下拉刷新" forState:MJRefreshStateIdle];
[self setTitle:@"松开🐴上刷新" forState:MJRefreshStatePulling];
[self setTitle:@"正在拼命刷新..." forState:MJRefreshStateRefreshing];
// 隐藏时间
self.lastUpdatedTimeLabel.hidden = YES;
// 自动切换透明度
self.automaticallyChangeAlpha = YES;
}
return self;
}
@end
如果需要完全自定义我们一颗继承MJRefresh的基类MJRefreshHeader再 实现父类的方法
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
UISwitch *s = [[UISwitch alloc] init];
[self addSubview:s];
self.s = s;
UIImageView *logo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"MainTitle"]];
[self addSubview:logo];
self.logo = logo;
// self.xmg_height = 70;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.logo.xmg_centerX = self.xmg_width * 0.5;
self.logo.xmg_y = - 3 * self.logo.xmg_height;
self.s.xmg_centerX = self.xmg_width * 0.5;
self.s.xmg_centerY = self.xmg_height * 0.5;
}
子类化第三方控件的父类,自定义我们自己的类
以AFN框架为例
情境:在发送请求数据的时候,经常需要在请求头带上手机的数据,在此情况下
在此情况下我们可以自定义一个类继承AFHTTPSessionManager
在自定义的类中实现initwithbaseurl..方法,在这个方法中统一请求的参数
@implementation XMGHTTPSessionManager
- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
if (self = [super initWithBaseURL:url sessionConfiguration:configuration]) {
[self.requestSerializer setValue:[UIDevice currentDevice].model forHTTPHeaderField:@"Phone"];
[self.requestSerializer setValue:[UIDevice currentDevice].systemVersion forHTTPHeaderField:@"OS"];
}
return self;
}