当我们开发一个新的app的时候,新建一个项目之后 要对这个项目的基本环境进行设置,
首先是启动图片 和app图标(桌面显示的那个)
- 设置启动图片有两种方式
- LaunchScreen
- LaunchImage
优先级:LaunchScreen > LaunchImage
在xcode配置了,不起作用 1.清空xcode缓存 2.直接删掉程序 重新运行
如果是通过LaunchImage设置启动界面,那么屏幕的可视范围由图片决定
注意:如果使用LaunchImage,必须让你的美工提供各种尺寸的启动图片
LaunchImage 各种图片:
iPhone Portrait ios 5,6
1x:320 * 480
2x:640 * 960
Retina 4:640 * 1136
iPhone Portrait iOS 8,9
Retina HD 5.5'':1242 * 2208
Retina HD 4.7'':750 * 1334
5.8: 2436 * 1125 @3x
iPhone Landscape iOS 8,9
Retina HD 5.5'':2208 * 1242
iPhone Portrait iOS 7-9
2x:640 * 960
Retina 4:*640 1136
iPhone Portrait iOS 12 +
iPhone XR:828px x 1792px
iPhone XS Max: 1242px x 2688pxiPhoneLandscape iOS 12 +
iPhone XR:792px * 828px
iPhone XS Max: 2688px * 1242px
/****************Ipad********************/
iPad Portrait iOS 7,8
1x 768 × 1024 pixels
2x 1536 × 2048 pixels
** iPad Landscape iOS 7,8**
1x LaunchImage.png 1024 × 768 pixels
2x LaunchImage@2x.png 1536 × 2048 pixels
LaunchScreen:Xcode6开始才有
LaunchScreen好处:
1.自动识别当前真机或者模拟器的尺寸
2.只要让美工提供一个可拉伸图片
3.展示更多东西
LaunchScreen底层实现:把LaunchScreen截屏,生成一张图片.作为启动界面
项目架构
项目架构(结构)搭建:主流结构(UITabBarController + 导航控制器)
-> 项目开发方式 1.storyboard 2.纯代码
确定项目主架构 是用代码还用storyboard,
-
程序启动的过程
1.先执行main函数->UIApplicationMain下一步 创建UIApplication(1.打开网页,发短信,打电话 2.设置应用程序提醒数字 3.设置联网状态 4.设置状态栏)
2.创建AppDelegate代理对象,并且成为UIApplication代理,(监听整个app生命周期,处理内存警告)
3.开启主运行循环,保证程序一直运行(runloop:每一个线程都有runloop,主线程有一个runloop自动开启)
4.加载info.plist,判断是否指定了main.storyboard,如果指定,就会去加载1.创建窗口
2.设置根控制器
3.显示窗口
- 如果我们使用代码方式, 首相要在这个appdelegate.m里面手动更改系统的跟控制器
// 程序启动的时候就会调用
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//1.创建主窗口
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//2.更改跟控制器
UITabBarController *taBarController = [[UITabBarController alloc] init];
self.window.rootViewController = taBarController;
//添加子控制器
//3. 显示主窗口
[self.window makeKeyAndVisible];
return YES;
}
- 添加tabar控制器的子控制器,设置属性的时候
问题:
1.选中的图片被渲染
2.选中标题颜色:黑色 标题字体大
3.发布按钮显示不出来
//精华
essenceNav.tabBarItem.title = @"精华";
essenceNav.tabBarItem.image = [UIImage imageNamed:@"imageName"];
essenceNav.tabBarItem.selectedImage = [UIImage imageNamed:@"imageName"];
//新帖
newNav.tabBarItem.title = @"新帖";
newNav.tabBarItem.image = [UIImage imageNamed:@"imageName"];
newNav.tabBarItem.selectedImage = [UIImage imageNamed:@"imageName"];
这是因为Xcode自动帮我们把图片渲染,这里我们改变他的渲染方式,
可以定义一个UIImage的分类:
+ (instancetype)imageOriginalWithImageName:(NSString *)imageName
{
UIImage *image = [UIImage imageNamed:imageName];
return [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
}
处理Tabbar颜色和字体
在自定义的Tabbarcontroller的load方法
/ 只会调用一次
+ (void)load
{
// UITabBarItem *item = [UITabBarItem appearanceWhenContainedInInstancesOfClasses:<#(nonnull NSArray<Class<UIAppearanceContainer>> *)#>];io9
UITabBarItem *item = [UITabBarItem appearanceWhenContainedIn: self, nil];
// 设置按钮选中标题的颜色:富文本:描述一个文字颜色,字体,阴影,空心,图文混排
// 创建一个描述文本属性的字典
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
dic[NSForegroundColorAttributeName] = [UIColor blackColor];
[item setTitleTextAttributes:dic forState:UIControlStateSelected];
//设置字体大小// 设置字体尺寸:只有设置正常状态下,才会有效果
NSMutableDictionary *fontDic = [NSMutableDictionary dictionary];
fontDic[NSFontAttributeName] = [UIFont systemFontOfSize:8];
[item setTitleTextAttributes:fontDic forState:UIControlStateNormal];
}
如果前一天有没有做完的地方我们 可以使用
#warning TODO
来提示我们有哪些地方没有做的
发布按钮的图片不显示 或者现实的的位置不对
选中按钮的图片被渲染 -> iOS7之后默认tabBar上按钮图片都会被渲染 1.修改图片 2.通过代码
选中按钮的标题颜色:黑色 标题字体大 -> 对应子控制器的tabBarItem
发布按钮显示不出来 分析:为什么其他图片可以显示,我的图片不能显示 => 发布按钮图片太大,导致显示不出来
图片太大,系统帮你渲染 => 能显示 => 位置不对 => 高亮状态达不到
publishVc.tabBarItem.imageInsets = UIEdgeInsetsMake(6, 0, -6 , 0);
解决:不能修改图片尺寸, 效果:让发布图片居中
自定义tabbar
- (UIButton *)plusBtn
{
if (_plusBtn == nil) {
UIButton *pBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[pBtn setImage:[UIImage imageOriginalWithImageName:@"imageName"] forState:UIControlStateNormal];
[pBtn setImage:[UIImage imageOriginalWithImageName:@"imageName"] forState:UIControlStateHighlighted];
[pBtn sizeToFit];
[self addSubview:pBtn];
_plusBtn = pBtn;
}
return _plusBtn;
}
- (void)layoutSubviews
{
[super layoutSubviews];
//调整tabbarbutton的尺寸位置
CGFloat btnW = self.bounds.size.width / (self.items.count+1);
CGFloat btnH = self.bounds.size.height;
CGFloat btnX = 0;
int i = 0;
for (UIView *subView in self.subviews)
{
if ([subView isKindOfClass:NSClassFromString(@"UITabBarButton")])
{
if (i == 2)
{
i+=1;
}
btnX = btnW * i;
subView.frame = CGRectMake(btnX, 0, btnW, btnH);
i ++;
}
}
self.plusBtn.center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
}
/******************/
- (void)setTabbar
{
BDJTarbar *tabbar = [[BDJTarbar alloc] init];
[self setValue:tabbar forKeyPath:@"tabBar"];
}
抽取UIView的分类
- (void)setX:(CGFloat)x
{
CGRect myFrame = self.frame;
myFrame.origin.x = x;
self.frame = myFrame;
}
- (CGFloat)x
{
return self.frame.origin.x;
}
-(void)setY:(CGFloat)y
{
CGRect myFrame = self.frame;
myFrame.origin.y = y;
self.frame = myFrame;
}
- (CGFloat)y
{
return self.frame.origin.y;
}
-(void)setWidth:(CGFloat)width
{
CGRect myFrame = self.frame;
myFrame.size.width = width;
self.frame = myFrame;
}
- (CGFloat)width
{
return self.frame.size.width;
}
-(void)setHeight:(CGFloat)height
{
CGRect myFrame = self.frame;
myFrame.size.height = height;
self.frame = myFrame;
}
- (CGFloat)height
{
return self.frame.size.height;
}
- (void)setCenter:(CGPoint)center
{
self.center = center;
}
- (CGPoint)center
{
return self.center;
}
- (void)setCenterX:(CGFloat)centerX
{
CGPoint myCenter = self.center;
myCenter.x = centerX;
self.center = myCenter;
}
- (CGFloat)centerX
{
return self.center.x;
}
- (void)setCenterY:(CGFloat)centerY
{
CGPoint myCenter = self.center;
myCenter.y = centerY;
self.center = myCenter;
}
- (CGFloat)centerY
{
return self.center.y;
}
设置一个pch头文件的
在other里面找到pch
然后在在bulid setting里面打开prefix header
这个路径
项目文件夹/子路径/BuDeJieHeader.pch
这里的子路径就是你的pch所在的路径,也可以用简单方法直接拖动.pch
文件到这个里面就会生出全路径 ,然后自己根据需要的进行删除。
设置导航条的按钮
在进行设置之前,我们要先区分好几个常用的概念
UIBarButtonItem:描述按钮具体的内容
UINavigationItem:设置导航条上内容(左边,右边,中间)
tabBarItem: 设置tabBar上按钮内容(tabBarButton)
由于设置的UIBarButtonItem方法相同,所以我们可以创建一个UIBarButtonItem的分类用于快速创建Item。
- 设置navigationbarItem
UIButton *leftBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[leftBtn setImage:[UIImage imageNamed:@"ImageName"] forState:UIControlStateNormal];
[leftBtn setImage:[UIImage imageNamed:@"ImageName"] forState:UIControlStateHighlighted];
[leftBtn addTarget:self action:@selector(leftAction) forControlEvents:UIControlEventTouchUpInside];
[leftBtn sizeToFit];
UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithCustomView:leftBtn];
self.navigationItem.leftBarButtonItem = leftItem;
//右边的按钮分类方法
self.navigationItem.rightBarButtonItem = [UIBarButtonItem itemWithImage:[UIImage imageNamed:@"ImageName"] hightImage:[UIImage imageNamed:@"ImageName"] target:self action:@selector(leftAction)];
//标题
self.navigationItem.titleView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ImageName"]];
处理导航条标题
+ (void)load
{
UINavigationBar *navBar = [UINavigationBar appearanceWhenContainedIn:self, nil];
//ios 之后推荐使用appearanceWhenContainedInInstancesOfClasses
//只要通过模型进行设置,都是使用富文本进行设置
//设置标题
NSMutableDictionary *titleDic = [NSMutableDictionary dictionary];
titleDic[NSFontAttributeName] = [UIFont systemFontOfSize:20];
[navBar setTitleTextAttributes:titleDic];
// 设置导航条背景图片
[navBar setBackgroundImage:[UIImage imageNamed:@"imageName"] forBarMetrics:UIBarMetricsDefault];
}
设置导航控制器的跳转逻辑
再有tabbar的时候我们跳转之后需要净底部的tabbar隐藏 然后返回的时候 还需要将它显示出来。
SettingTVController *settingTVC = [[SettingTVController alloc] init];
//必须在跳转之前设置
settingTVC.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:settingTVC animated:YES];
- 统一设置返回按钮
//在自定义的navigationController里面重写push方法
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (self.childViewControllers.count>0) //非根控制器
{
viewController.hidesBottomBarWhenPushed = YES;
viewController.navigationItem.leftBarButtonItem = [UIBarButtonItem backItemWithImage:[UIImage imageNamed:@"imageName"] hightImage:[UIImage imageNamed:@"imageName"] target:self action:@selector(back) title:@"返回"];
}
[super pushViewController:viewController animated:YES];
}
- (void)back
{
[self popViewControllerAnimated:YES];
}
- 滑动返回
如果我们自定义了返回的按钮的话 系统自定华东返回手就会失效,
这个时候我们需要手动的打开代理方法UIGestureRecognizerDelegate
设置方法 self.interactivePopGestureRecognizer.delegate = self;
这时候我们需要实现一个代理方法,如果不实现的话的,当childViewController的个数为一个的时候会就会导致我们的程序进入假死状态,程序还在运行,但是页面已经不可以动了
@interface MyNavigationController ()<UIGestureRecognizerDelegate>
- (void)viewDidLoad
{
[super viewDidLoad];
self.interactivePopGestureRecognizer.delegate = self;
}
#pragma mark - UIGestureRecognizerDelegate -
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(nonnull UITouch *)touch
{
return self.childViewControllers.count> 1;
}
- 如果我们想要全屏滑动都可以返回的话,就不能用系统的手势,因为他只有在靠近左边的时候才可以返回,
这个时候我们首先要做的就将系统的手势交互关掉
// 禁止之前手势
self.interactivePopGestureRecognizer.enabled = NO;
打印self.interactivePopGestureRecognizer
可以显示以下信息
<UIScreenEdgePanGestureRecognizer: 0x10121fb60;
state = Possible; delaysTouchesBegan = YES;
view = <UILayoutContainerView 0x101218390>;
target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x10121fa20>)>>
自定义个滑动手势,但是手势的事件处理还是用原来的方法,这个时候我们就可以使用系统的打印出来的信息
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self.interactivePopGestureRecognizer.delegate action:@selector(handleNavigationTransition:)];
[self.view addGestureRecognizer:pan];
Cocoapods的安装
因为mac都是自带ruby的所以我们只需要根据下面的步骤来就可以了
1.先升级Gem
sudo gem update --system
2.切换cocoapods的数据源
【先删除,再添加,查看】
gem sources --remove https://rubygems.org/
gem sources -a https://ruby.taobao.org/ (淘宝已经不更新了,所以这个不可以使用了,我们可以使用,下面这个)
gem sources --add https://gems.ruby-china.com #添加国内镜像源
gem sources -l//查看数据源
3.安装cocoapods
sudo gem install cocoapods
或者(如10.11系统)sudo gem install -n /usr/local/bin cocoapods
4.将Podspec文件托管地址从github切换到国内的oschina
【先删除,再添加,再更新】
pod repo remove master
pod repo add master http://git.oschina.net/akuandev/Specs.git
pod repo add master https://gitcafe.com/akuandev/Specs.git
pod repo update
5.设置pod仓库
pod setup
setup这一步会很慢或者无限卡 Setting up CocoaPods master repo,这是因为要在github下载代码,我这一步一直有问题,查询网上都说要将specs仓库镜像换成gitcafe上的镜像或者是 oschina 上的镜像即:
pod repo remove master
$ pod repo add master https://git.coding.net/CocoaPods/Specs.git
//或者 pod repo add master https://git.oschina.net/akuandev/Specs.git
但是执行 pod repo remove master之后老是提示[!] repo master does not exist;然后执行 pod repo add master
https://git.coding.net/CocoaPods/Specs.git
之后又会提示[!] To setup the master specs repo, please run pod setup.
然后就无限卡死这两步,不知道是因为cocoapods版本的问题还是网络的问题,
最后直接手动将代码git到本地得以解决问题:
git clone https://git.coding.net/CocoaPods/Specs.git ~/.cocoapods/repos/master
git完成之后如下:
1、sudo gem update --system
2、gem install cocoapods
6.测试
【如果有版本号,则说明已经安装成功】
pod --version
7.利用cocoapods来安装第三方框架
01 进入要安装框架的项目的.xcodeproj同级文件夹
02 在该文件夹中新建一个文件podfile (cd到当前工程的文件夹 在终端使用touch Podfile命令创建出一个文件,然后用文本编辑打开)
03 在文件中告诉cocoapods需要安装的框架信息
a.该框架支持的平台
b.适用的iOS版本
c.框架的名称
d.框架的版本
具体使用:
cd 项目路径
pod init #创建默认的 Podfile(可跳过)
vim Profile #编辑(没有会创建)Profile配置文件
pod install #安装或删除第三方库
pod install --no-repo-update #安装第三方库,不更新本地索引
pod update #更新到最新版本或指定版本
例如:
platform :ios, '8.0'
pod 'SDWebImage', '~> 5.0'
platform :ios, '8.0' #支持的iOS版本
target 'Demo' do #指定的项目
pod 'AFNetworking' #要添加的第三方库名称及指定版本
end
8.安装
pod install --no-repo-update下载不需要更新pod
pod update --no-repo-update
如果想搜索的话可以使用 pod search XXX
9.说明
platform :ios, '8.0' 用来设置所有第三方库所支持的iOS最低版本
pod 'SDWebImage','~>2.6' 设置框架的名称和版本号
版本号的规则:
'>1.0' 可以安装任何高于1.0的版本
'>=1.0' 可以安装任何高于或等于1.0的版本
'<1.0' 任何低于1.0的版本
'<=1.0' 任何低于或等于1.0的版本
'~>0.1' 任何高于或等于0.1的版本,但是不包含高于1.0的版本
'~>0' 任何版本,相当于不指定版本,默认采用最新版本号
10.使用pod install命令安装框架后的大致过程:
01 分析依赖:该步骤会分析Podfile,查看不同类库之间的依赖情况。如果有多个类库依赖于同一个类库,但是依赖于不同的版本,那么cocoaPods会自动设置一个兼容的版本。
02 下载依赖:根据分析依赖的结果,下载指定版本的类库到本地项目中。
03 生成Pods项目:创建一个Pods项目专门用来编译和管理第三方框架,CocoaPods会将所需的框架,库等内容添加到项目中,并且进行相应的配置。
04 整合Pods项目:将Pods和项目整合到一个工作空间中,并且设置文件链接。
- 有一些框架是不支持pods,要在添加之前查看一下how to use有没有说明,
或者到框架文件里面查看有没有podspec后缀的文件有的话基本可以使用
CocoaPods卸载;
终端使用
sudo gem uninstall CocoaPods #卸载CocoaPods
AFNetworking 使用
如果我们请求的时候出现这个错误的话:unacceptable content-type: text/html" 响应头
客户端从服务情请求的数据 要遵守http协议
请求 分为请求头 请求体
头里面会告诉服务器 客户端支持那些格式,
请求体 一般是在post里面的才有的,把参数发到请求体里面
响应 包含相应头和响应体,响应头里面就有content-type
他是用来描述服务器给你的数据的格式
返回的数据就在响应体里面。
我们那要做的就是让afn接受content-type这种格式
我们来改afn到他的给他添加进去 text/html
解析模式,但是要注意,一定是你的数据本身就是json但是服务器的响应头显示的是text/html。
图片设置圆角,
在ios9 之前如果我们直接对layer的属性进行设置的话,图片过多会造成 页面卡顿的效果,所以一般都是对图片进行处理
// 1.开启图形上下文
// 比例因素:当前点与像素比例
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
// 2.描述裁剪区域
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
// 3.设置裁剪区域;
[path addClip];
// 4.画图片
[image drawAtPoint:CGPointZero];
// 5.取出图片
image = UIGraphicsGetImageFromCurrentImageContext();
// 6.关闭上下文
UIGraphicsEndImageContext();
self.iconView.image = image;
如果是iOS9之后,设置头像圆角,iOS9苹果修复,不会出现卡顿的文图
_iconView.layer.cornerRadius = 30;
_iconView.layer.masksToBounds = YES;
tableViewCell的分割线
当我定义完一个cell,进行展示的时候我们会发现,cell的分割线在前面有一点距离
elf.tableView.separatorInset
处理cell分割线
1.自定义分割线
比如弄一个UIView加到上面去
2.系统属性(iOS8才支持)
self.tableView.separatorInset = UIEdgeInsetsZero;
在cell的初始化方法还要设置
self.layoutMargins = UIEdgeInsetsZero;
因为iOS8 之后xcode就自动给这些view割伤contentmargin。
这只方式可以实现分割线沾满屏幕,但是条件是要最低支持iOS才可以。
3.万能方式
重写在cell的setFrame方法
- (void)setFrame:(CGRect)frame
{
frame.size.height - =1;
[super setFrame:frame];
}
请求指示器 提示用户正在加载的信息
这个项目使用SVProgressHUD可以在pod直接引入,也可以到github上面下载引入到项目中
请求的时候使用
[SVProgressHUD showWithStatus:@"正在加载ing....."];
请求结束或者 界面消失的时候
// 销毁指示器
[SVProgressHUD dismiss];
command+option+回车可以在xib中让label的title文字换行
防止图片拉伸变形,如按钮的背景图片
这种纯色的,或者是那种只有边框的我们最好只拉伸他的中间部分
UIImage *image = [UIImage imageNamed:@"imageName"];
image = [image stretchableImageWithLeftCapWidth:image.size.width * 0.5 topCapHeight:image.size.height * 0.5];
使用xib定义的View尺寸 一般不可以直接设置,如果我们要重新设置的话
1.一个view从xib加载,需不需在重新固定尺寸 一定要在重新设置一下
2.在viewDidLoad设置控件frame好不好,开发中一般在viewDidLayoutSubviews布局子控件
处理文本占字符颜色
越复杂的东西越要封装,
当我们的额设置的东西只要一次的时候,而且使用的xib我们可以在aweakFromNib中设置,
当我们找这个占字符的先去头文件查找,attributePlaceholderString
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
dic[NSForegroundColorAttributeName] = [UIColor whiteColor];
self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder attributes:dic];
- 快速设置占字符颜色
这是看可以看懂field的内部,这是我们可以通过kvc进行设置。但是需要知道属性名字,首先,要获取属性名,1可以通过runtime来打印,2,可以用断点,用断点我们可以查看到
他有一个placeholderLabel的属性
这时候我们就可以利用KVC进行设置
UILabel *placeholderLabel = [self valueForKey:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor whiteColor];
如果我们想以后都使用可以自定义一个分类
.h
@property UIColor *placeholderColor;
.m
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
UILabel *placeholderLabel = [self valueForKey:@"placeholderLabel"];
placeholderLabel.textColor = placeholderColor;
}
- (UIColor *)placeholderColor
{
return nil;
}
- runtime设置
使用上面的设置方式,要特别注意设置的顺序需要先设置占位文字,在设置颜色,因为这个label是懒加载,如果我们没有设置文字的话,就为nil这时候我们设置颜色是没有效果的。所以我门完善一下。使用runtime。修改placeholder的set方法,和我们自己的设置的方法交换实现.
+ (void)load
{
// setPlaceholder
Method setPlaceholderMethod = class_getInstanceMethod(self, @selector(setPlaceholder:));
Method setXmg_PlaceholderMethod = class_getInstanceMethod(self, @selector(setXmg_Placeholder:));
method_exchangeImplementations(setPlaceholderMethod, setXmg_PlaceholderMethod);
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
// 给成员属性赋值 runtime给系统的类添加成员属性
// 添加成员属性
objc_setAssociatedObject(self, @"placeholderColor", placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 获取占位文字label控件
UILabel *placeholderLabel = [self valueForKey:@"placeholderLabel"];
// 设置占位文字颜色
placeholderLabel.textColor = placeholderColor;
}
- (UIColor *)placeholderColor
{
return objc_getAssociatedObject(self, @"placeholderColor");
}
// 设置占位文字
// 设置占位文字颜色
- (void)setXmg_Placeholder:(NSString *)placeholder
{
[self setXmg_Placeholder:placeholder];
self.placeholderColor = self.placeholderColor;
}
使用collectionView的时候有时候会出现下面不会刚好占满的情况,这时候我们要进行补充数据
当我们点击url跳转的时候可以使用webView也可以使用手机浏览器
- Safari openURL :自带很多功能(进度条,刷新,前进,倒退等等功能),必须要跳出当前应用
- UIWebView (没有功能) ,在当前应用打开网页,并且有safari,自己实现,UIWebView不能实现进度条
- SFSafariViewController:专门用来展示网页 需求:即想要在当前应用展示网页,又想要safari功能 iOS9才能使用
3.1 导入#import <SafariServices/SafariServices.h> - WKWebView :iOS8 (UIWebView升级版本,添加功能 1.监听进度 2.缓存)
4.1 导入#import <WebKit/WebKit.h>
WKWebView的使用
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.webView.frame = self.containerView.bounds;
}
- (void)viewDidLoad
{
[super viewDidLoad];
WKWebView *webView = [[WKWebView alloc] init];
//添加网页
[self.containerView addSubview:webView];
self.webView = webView;
// 展示网页
[webView loadRequest:[NSURLRequest requestWithURL:self.url]];
// KVO监听属性改变
/*
Observer:观察者
KeyPath:观察webView哪个属性
options:NSKeyValueObservingOptionNew:观察新值改变
KVO注意点.一定要记得移除
*/
[webView addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"canGoForward" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}
- (IBAction)goBackItemClick:(UIBarButtonItem *)sender
{
[self.webView goBack];
}
- (IBAction)goOnItemClick:(id)sender
{
[self.webView goForward];
}
- (IBAction)refreshItemClick:(UIBarButtonItem *)sender
{
[self.webView reload];
}
#pragma mark - KVO观察到新值会调用 -
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
self.goOnItem.enabled = self.webView.canGoForward;
self.goBackItem.enabled = self.webView.canGoBack;
self.title = self.webView.title;
self.progressView.progress = self.webView.estimatedProgress;
self.progressView.hidden = self.webView.estimatedProgress>=1;
}
- (void)dealloc
{
[self.webView removeObserver:self forKeyPath:@"canGoBack"];
[self.webView removeObserver:self forKeyPath:@"canGoForward"];
[self.webView removeObserver:self forKeyPath:@"title"];
[self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
}
获取缓存的大小
- 使用SDWebImage自带的方法实现
//sdWebImage自带的计算缓存大小的方法
NSUInteger cacheNumber = [SDImageCache sharedImageCache].getSize;
BDJLog(@"缓存大小%lu", cacheNumber);
利用NSFileManager计算文件的的大小
// NSFileManager
// attributesOfItemAtPath:指定文件路径,就能获取文件属性
// 把所有文件尺寸加起来
// 获取Caches文件夹路径
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 获取default文件路径
NSString *defaultPath = [cachePath stringByAppendingPathComponent:@"default/com.hackemist.SDWebImageCache.default/0ccb23cf1c79acaa461c1a43cddc6759.png"];
// 遍历文件夹所有文件,一个一个加起来
// 获取文件管理者
NSFileManager *mgr = [NSFileManager defaultManager];
// 获取文件属性
// attributesOfItemAtPath:只能获取文件尺寸,获取文件夹不对,
NSDictionary *attr = [mgr attributesOfItemAtPath:defaultPath error:nil];
// default尺寸
NSInteger fileSize = [attr fileSize];
NSLog(@"图片的大小 = %ld",fileSize);
- 利用FileManager删除缓存
- (NSString *)getSizeString
{
NSInteger totalSize = [self getMyFileSize:CachePath];
NSString *str = @"清除缓存";
if (totalSize > 1000 * 1000)
{
CGFloat fileSize = totalSize / 1000.0/1000.0;
str = [NSString stringWithFormat:@"%@(%.1fMB)", str, fileSize];
}
else if (totalSize > 1000 )
{
CGFloat fileSize = totalSize / 1000.0;
str = [NSString stringWithFormat:@"%@(%.1fKB)", str, fileSize];
}
else if (totalSize > 0)
{
CGFloat fileSize = totalSize;
str = [NSString stringWithFormat:@"%@(%.1fdB)", str, fileSize];
}
return str;
}
//自己去实现SDWebImage的底层
-(NSInteger)getMyFileSize:(NSString *)directoryPath
{
NSFileManager *mgr = [NSFileManager defaultManager];
//获取文件下所有的子路径
NSArray *subPaths = [mgr subpathsAtPath:directoryPath];
BDJLog(@"%@", subPaths);
//遍历他的子路径
NSInteger totalSize = 0;
for (NSString *subPath in subPaths)
{
NSString *fileCurrentPath = [directoryPath stringByAppendingPathComponent:subPath];
//判断是否是隐藏文件
if ([fileCurrentPath containsString:@".DS"]) continue;
//判断是否为文件夹
BOOL isDirectory;
//判断文件是否存在
BOOL isExist = [mgr fileExistsAtPath:fileCurrentPath isDirectory:&isDirectory];
if (!isExist || isDirectory) continue;
//获取文件的属性
NSDictionary *attri = [mgr attributesOfItemAtPath:fileCurrentPath error:nil];
//attributesOfItemAtPath:只能获取文件尺寸,获取文件夹不对,
NSInteger fileSize = [attri fileSize];
totalSize += fileSize;
}
BDJLog(@"%ld", totalSize);
return totalSize;
}
- 删除操作 -
{
NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *subPaths = [mgr subpathsAtPath:CachePath];
for (NSString *subPath in subPaths)
{
NSString * deleteStr = [CachePath stringByAppendingPathComponent:subPath];
[mgr removeItemAtPath:deleteStr error:nil];
}
[self.tableView reloadData];
}
封装文件管理业务类
+ (NSInteger)getFileSize:(NSString *)directoryPath
{
NSFileManager *mgr = [NSFileManager defaultManager];
BOOL isDirectory;
BOOL isExist = [mgr fileExistsAtPath:directoryPath isDirectory:&isDirectory];
if (!isExist||!isDirectory)
{
// 抛异常
// name:异常名称
// reason:报错原因
NSException *excp = [NSException exceptionWithName:@"pathError" reason:@"经输入正确的文件的路径" userInfo:nil];
//抛出
[excp raise];
}
// NSFileManager
// attributesOfItemAtPath:指定文件路径,就能获取文件属性
// 把所有文件尺寸加起来
NSArray *subPaths = [mgr subpathsAtPath:directoryPath];
NSInteger totalSize = 0;
for (NSString *subPath in subPaths)
{
NSString *filePath = [directoryPath stringByAppendingPathComponent:subPath];
//判断是否为隐藏文件
if ([filePath containsString:@".DS"]) continue;
//判断是否为文件
BOOL isDirectory;
//判断文件是否存在
BOOL isExist = [mgr fileExistsAtPath:directoryPath isDirectory:&isDirectory];
if (!isExist||!isDirectory) continue;
NSDictionary *fileDic = [mgr attributesOfItemAtPath:filePath error:nil];
NSInteger fileSize = [fileDic fileSize];
totalSize +=fileSize;
}
return totalSize;
}
+ (void)removeDirectoryPath:(NSString *)directoryPath
{
NSFileManager *mgr = [NSFileManager defaultManager];
//判断是否为文件夹
BOOL isDirectory;
//a判断是否存在
BOOL isExist = [mgr fileExistsAtPath:directoryPath isDirectory:&isDirectory];
if (!isExist || !isDirectory)
{
//抛出异常
NSException *excp = [NSException exceptionWithName:@"path error" reason:@"请输入正确的文件路径" userInfo:nil];
[excp raise];
}
//所有子路径
NSArray *subPaths = [mgr subpathsAtPath:directoryPath];
for (NSString *subPath in subPaths)
{
NSString *filePath = [directoryPath stringByAppendingPathComponent:subPath];
[mgr removeItemAtPath:filePath error:nil];
}
}
计算过程如果太多的话 会造成手机太卡,所有我们需要加入子线程
//完整实现,不能让计算文件的过程影响手机的流畅
+ (void)getFileSize:(NSString *)directoryPath completion:(void (^)(NSInteger))completion
{
NSFileManager *mgr = [NSFileManager defaultManager];
BOOL isDirectory;
BOOL isExist = [mgr fileExistsAtPath:directoryPath isDirectory:&isDirectory];
if (!isExist||!isDirectory)
{
// 抛异常
// name:异常名称
// reason:报错原因
NSException *excp = [NSException exceptionWithName:@"pathError" reason:@"经输入正确的文件的路径" userInfo:nil];
//抛出
[excp raise];
}
// NSFileManager
// attributesOfItemAtPath:指定文件路径,就能获取文件属性
// 把所有文件尺寸加起来
dispatch_async(dispatch_get_global_queue(0, 0), ^
{
NSArray *subPaths = [mgr subpathsAtPath:directoryPath];
NSInteger totalSize = 0;
for (NSString *subPath in subPaths)
{
NSString *filePath = [directoryPath stringByAppendingPathComponent:subPath];
//判断是否为隐藏文件
if ([filePath containsString:@".DS"]) continue;
//判断是否为文件
BOOL isDirectory;
//判断文件是否存在
BOOL isExist = [mgr fileExistsAtPath:directoryPath isDirectory:&isDirectory];
if (!isExist||!isDirectory) continue;
NSDictionary *fileDic = [mgr attributesOfItemAtPath:filePath error:nil];
NSInteger fileSize = [fileDic fileSize];
totalSize +=fileSize;
}
//计算完成,返回主线程
dispatch_sync(dispatch_get_main_queue(), ^
{
if (completion)
{
completion(totalSize);
}
});
});
}
按钮的状态分析
一、按钮的状态
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状态,继续保持当前状态
当我们点击已经selected的按钮的时候 还会出现高亮的效果,
去掉高亮,因为我们点击一次按钮他就会调用一次sethight方法,所以我们要重写他的setHilight方法,
下划线的宽度
//下划线的设置的第一种方法
CGSize textSize = [firstTitleBtn.titleLabel.text sizeWithFont:firstTitleBtn.titleLabel.font];
//下划线的设置的第二种方法
NSMutableDictionary *attribus = [NSMutableDictionary dictionary];
attribus[NSFontAttributeName] = firstTitleBtn.titleLabel.font;
CGSize testSize = [firstTitleBtn.currentTitle sizeWithAttributes:attribus];
//第三种处理发放
underLineView.width = firstTitleBtn.titleLabel.width +10;
全屏穿透效果
通过控制tableView的contentInset属性来控制显示
在viewDidload的添那边距 由于导航栏的存在,他会帮我们自动添加64.
如果在其他地方的设置didselect。 他的那边距=64.现在设置,直接是将他的那边的改为你设置的数据40
self.tableView.contentInset = UIEdgeInsetsMake(BDJTilteViewHight, 0, 0, 0);
//self.automaticallyAdjustsScrollViewInsets = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
bundle 捆绑包
用来放资源的,不需要编译的 mp3 mp4 图片·
我们创建一个xib,如果我们加载的时候,我们使用的用
[NSbundle mainBundle]loadnibwithName:
因为我们的文件放到外面。他会将我们的文件 放到mainBundle里面去,storyboard等xib文件
因为我们自己的bundle文件也是在mainbundle里面,所以我们要去的自己的文件也要用mainbundle
所以 我们要先找到我们mytest.bundle要找到我们的地址
URl *myUrl = [NSbundle mainBundle] Urlfor;
[nsbundle bundleWithUrl];
我们找到bundle 经常用的是找到里面文件的全路径
imagenamed的方法 是去mainbundle里面找文件的,如果在其他地方是找不到的,所以要使用imageWithContentfile
找到文件bundle路径的方法
// 方法1
UIImage *image = [UIImage imageNamed:@"MyTest.bundle/Test"];
// 方法2
NSString *file1 = [[NSBundle mainBundle] pathForResource:@"MyTest.bundle/Test" ofType:@"png"];
UIImage *image1 = [UIImage imageWithContentsOfFile:file1];
// 方法3
NSString *path = [[NSBundle mainBundle] pathForResource:@"MyTest" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:path];
NSString *file2 = [bundle pathForResource:@"Test" ofType:@"png"];
UIImage *image2 = [UIImage imageWithContentsOfFile:file2];
tableView的重要属性
tableView.contentSize.height = 内容的高度
tableView.contentOffsetY = frame的顶部和contensize的顶部的差值,
tableView.contentInset = 在内容周边添加的间距,始终粘着内容
tableVIew的footerView和tableVIewheaderView这些都是内容,添加了之后,会增加内容的高度。
添加的子控件,不会增加内容的size。
ITableView *tableView = [[UITableView alloc] init];
tableView.backgroundColor = [UIColor orangeColor];
tableView.delegate = self;
tableView.dataSource = self;
tableView.frame = CGRectMake(50, 100, 200, 300);
[self.view addSubview:tableView];
//内边距
// tableView.contentInset = UIEdgeInsetsMake(64, 0, 49, 0);
//headerVIew
UIView *headerView = [[UIView alloc] init];
headerView.frame = CGRectMake(0, 0, tableView.frame.size.width, 64);
headerView.backgroundColor = [UIColor yellowColor];
tableView.tableHeaderView = headerView;//contentSize 944
//footView
UIView *footerView = [[UIView alloc] init];
footerView.frame = CGRectMake(0, 0, tableView.frame.size.width, 64);
footerView.backgroundColor = [UIColor redColor];
tableView.tableFooterView = footerView;//contentSize 1008
//添加额外的子l控件
UIView *header2View = [[UIView alloc] init];
header2View.frame = CGRectMake(0, -64, tableView.frame.size.width, 64);
header2View.backgroundColor = [UIColor redColor];
[tableView addSubview:header2View ];//contentSize 1008
懒加载子控制器的View
方法一,传递参数,知道添加第几个控制器
//懒加载子控制器的View
- (void)addChildViewInScrollView:(NSInteger)index
{
UIViewController *currentVC = self.childViewControllers[index];
//如果Viewi已经被加载过
if (currentVC.isViewLoaded) return;
//取出VIew
UIView *childView = currentVC.view;
//设置子控制器View的gframe
childView.frame = CGRectMake(self.mainScrollView.width * index, 0, self.mainScrollView.width, self.mainScrollView.height);
[self.mainScrollView addSubview:childView];
}
第二种方式
CGFloat scrollViewW = self.mainScrollView.width;
NSInteger index = self.mainScrollView.contentOffset.x / scrollViewW;
UIViewController *currentVC = self.childViewControllers[index];
//如果Viewi已经被加载过 判断方法还可以有view的superView是否有值
//if (currentVC.isViewLoaded) return;
//取出VIew
UIView *childView = currentVC.view;
//设置子控制器View的gframe
childView.frame = self.mainScrollView.bounds;
这种设置方式 注意设置scrollView偏移量的时候要使用
elf.mainScrollView.contentOffset = CGPointMake(offSetX, self.mainScrollView.contentOffset.y);
监听状态栏和tabBar按钮的点击
当当前视图中的scrollView有个且 scrollstotop都是YES的时候这个属性是无法执行的,因此,我们要把除了当前要懂得视图之外的其他的视图改成NO;
//监听tabBar的点击
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(nonnull UIViewController *)viewController
//{
// BDJLog(@"tabBar点击了");
}
//监听状态栏的点击
//- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
//{
// if ([touches.anyObject locationInView:nil].y <= 20)
// {
// BDJLog(@"点击了状态栏");
// }
//}
自定义上拉加载和下拉刷新控件
图片处理一些操作
// 在周边加一个边框为1的透明像素
- (UIImage *)imageAntialias
{
CGFloat border = 1.0f;
CGRect rect = CGRectMake(border, border, self.size.width - 2 *border, self.size.height - 2 * border);
UIImage *img = nil;
UIGraphicsBeginImageContext(rect.size);
[self drawInRect:CGRectMake(-1, -1, self.size.width, self.size.height)];
img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContext(self.size);
[img drawInRect:rect];
UIImage *antiImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return antiImage;;
}
- (UIImage *)circleImage
{
//开启图片上下文
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);
//描绘裁剪区域
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
//裁剪
[path addClip];
//画图片
[self drawAtPoint:CGPointZero];
//5. 取出图片
UIImage *circleImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return circleImage;
}
+(UIImage *)circleImageWithName:(NSString *)name
{
return [[self imageNamed:name] circleImage];
}
计算某个Label的Size,内容确定,高度变化
BDJTopic *topic = self.topics[indexPath.row];
CGFloat cellHight = 0;
cellHight += 55;
CGSize textMaxSize = CGSizeMake(kSCREEN_WIDTH - BDJMargin * 2, MAXFLOAT);
cellHight += [topic.text sizeWithFont:[UIFont systemFontOfSize:15] constrainedToSize:textMaxSize].height + BDJMargin;
CGSize newCellSize = [topic.text boundingRectWithSize:textMaxSize options:nil attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15]} context:nil].size;
//工具跳的高度
cellHight += 35 + BDJMargin;
// 这2个方法只适合计算单行文字的宽高
// [topic.text sizeWithFont:[UIFont systemFontOfSize:15]].width;
// [UIFont systemFontOfSize:15].lineHeight;
估算cell的高度
self.tableView.estimatedRowHeight = 100;
由于tableView的heightForRow方法的调用频率太高,所以要提前估算cell的高度,这样系统就会根据估算的cell高度来创建一开始的的cell。苹果的一次性计算,是因为滚动条要提前根据内容的高度,计算出出自己的长度。这个计算不是特别的准确,如果设置了估算高度之后,他会根据大概有多少个cell,然后计算cellforrow方法,
预设高度与实质高度差别太大的话,会是滚动条闪烁,IOS7,之后才可以使用,
优点,降低heifhtForRow方法调用,延迟执行计算滚动条的高度。
缺点:滚动条长度不确定,不稳定,甚至卡顿
用xib创建的View
xib 起名字的时候,要注意更控制器的额名字不要相似
有时候我们创建的View看不见,如果我们打印出来的,他的frame有值,但是等一会打印,他的frame就没有值,默认丛xib加载的View的autoresizingMask = YES,autoresizing默认会跟着父控制器的伸缩,
一般来说1.xib加载的View会是YES ;2.控制器的View
假设我们的cell 刚开始 size = 320 170;创建的中间的xibView 的size = 240 80;如果dangcell的frame发生改变的时候,370 85;这是xib创建的View也会相应地发生改变。变成240+ 50, 80-85 <0;就会变成290,0;所以就会发生我们看不到的情况
cell的显示顺讯,先调用,个数,-> 每一行的高度->contensize - >
处理label现实的数字
//设置播放数
if (topic.playcount > 10000)
{
self.playCountLabel.text = [NSString stringWithFormat:@"%.1f播放", topic.playcount/ 10000.0];
}
else
{
self.playCountLabel.text = [NSString stringWithFormat:@"%ld播放", topic.playcount];
}
//播放时间
self.playTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", topic.voicetime / 60, topic.voicetime % 60];
使用AFNetWork监听网络
注意:要手动开始监听方法
//开始监听网络
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
根据不同的网络情况,做一些操作
//根据网络状态来判断加载图片
AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
BDJLog(@"是否可用%d", mgr.isReachable);
//获得原图,SDWebImage是根据url作为key
UIImage *origalImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:@"imageUrl"];
if (origalImage) //图片已经被下载过了
{
self.topicVoiceImageView.image = origalImage;
}
else //下载图片
{
if (mgr.isReachableViaWiFi) //WIFI网络 ,下载原图
{
BDJLog(@"wifi下载");
}
else if (mgr.isReachableViaWWAN)
{
NSLog(@"3G/4G下载");
#warning downloadOriginImageWhen3GOr4G配置项的值需要从沙盒里面获取
//3G/4G网络下需要下载原图
BOOL downloadOriginImageWhen3GOr4G = YES;
if (downloadOriginImageWhen3GOr4G)
{
}
else
{
}
}
else //没有网络的情况下
{
BDJLog(@"没有网络");
}
}
当使用SDWebImage的时候,要注意稳定内存
只清空内存缓存,沙盒不清
[[SDImageCache sharedImageCache]clearMemory ];