小程故事


  • 首页

  • 归档

NSMutableAttributedString

发表于 2018-12-07

初始化

常用

1
2
3
- (instancetype)initWithString:(NSString *)str;
- (instancetype)initWithString:(NSString *)str attributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attrs;
- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr;

非常用

1
2
3
- (nullable instancetype)initWithURL:(NSURL *)url options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options documentAttributes:(NSDictionary<NSAttributedStringDocumentAttributeKey, id> * __nullable * __nullable)dict error:(NSError **)error NS_AVAILABLE(10_4, 9_0);
- (nullable instancetype)initWithData:(NSData *)data options:(NSDictionary<NSAttributedStringDocumentReadingOptionKey, id> *)options documentAttributes:(NSDictionary<NSAttributedStringDocumentAttributeKey, id> * __nullable * __nullable)dict error:(NSError **)error NS_AVAILABLE(10_0, 7_0);
- (nullable instancetype)initWithFileURL:(NSURL *)url options:(NSDictionary *)options documentAttributes:(NSDictionary* __nullable * __nullable)dict error:(NSError **)error NS_DEPRECATED_IOS(7_0, 9_0, "Use -initWithURL:options:documentAttributes:error: instead") __TVOS_PROHIBITED;

例如:

1
2
NSData * data = [@"<html>text<\html>" dataUsingEncoding:NSUnicodeStringEncoding];
NSMutableAttributedString * text = [[NSMutableAttributedString alloc] initWithData:data options:@{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType} documentAttributes:nil error:nil];

其中 NSDocumentTypeDocumentAttribute 这个属性有如下的几种取值:

  1. NSPlainTextDocumentType (普通的文本)
  2. NSRTFTextDocumentType (富文本)
  3. NSRTFDTextDocumentType (带有附件的富文本)
  4. NSHTMLTextDocumentType (HTML格式的文本)

常用属性

属性 名 值类型 默认值
NSFontAttributeName 字体 UIFont Helvetica(Neue) 12
NSParagraphStyleAttributeName 段落样式 NSParagraphStyle defaultParagraphStyle
NSForegroundColorAttributeName 前景色 UIColor blackColor
NSBackgroundColorAttributeName (文本)背景色 UIColor nil: no background
NSLigatureAttributeName 连笔 NSNumber 这个属性的取值为整数。默认值为1,代表使用默认的连体字符; 取值0,表示没有连体字符。
NSKernAttributeName 间距 NSNumber 字符间距,取正值时间距增加,取负值时间距减小,0时间距无效
NSStrikethroughStyleAttributeName 删除线 NSNumber 取整数值默认为0,无删除线
NSUnderlineStyleAttributeName 下划线 NSNumber 取整数值默认为0,无下划线
NSStrokeColorAttributeName 填充颜色 UIColor 默认为 nil: 此时颜色同 foreground color
NSStrokeWidthAttributeName 填充(描边,加粗) NSNumber 正值为中空的效果,负值为填充的效果,默认值为0
NSShadowAttributeName 阴影 NSShadow 默认是nil,没有阴影效果
NSTextEffectAttributeName 文本特殊效果 NSString 默认是nil,没有特殊的文字效果
NSAttachmentAttributeName 文本附件,插入图片 NSTextAttachment 默认 nil
NSLinkAttributeName 链接 NSURL or NSString NSURL (preferred) or NSString
NSBaselineOffsetAttributeName 基准线偏移 NSNumber 默认0,垂直方向上,整数往上,负数往下
NSUnderlineColorAttributeName 下划线颜色 UIColor 默认 nil: 同 foreground color
NSStrikethroughColorAttributeName 删除线颜色 UIColor 默认 nil: 同 foreground color
NSObliquenessAttributeName 倾斜 NSNumber 默认0,正向右倾斜,负向左倾斜
NSExpansionAttributeName 扁平化 NSNumber 默认0,正拉伸,负压缩
NSWritingDirectionAttributeName 文字方向 NSArray of NSNumbers
NSVerticalGlyphFormAttributeName 水平或竖直文本 NSNumber

常用方法

iOS 深拷贝&浅拷贝

发表于 2018-10-29
对形象类型 调用方法 副本对象类型 是否产生新对象 集合中的元素
NSString copy NSString N
NSString mutableCopy NSMutableString Y
NSMutableString copy NSString Y
NSMutableString mutableCopy NSMutableString Y
Model copy Model Y
Model mutableCopy Model Y
NSArray copy NSArray N 浅拷贝(指针拷贝)
NSArray mutableCopy NSMutableArray Y 浅拷贝(指针拷贝)
NSMutableArray copy NSArray Y 浅拷贝(指针拷贝)
NSMutableArray mutableCopy NSMutableArray Y 浅拷贝(指针拷贝)

iOS WKWebKit

发表于 2018-10-22

WKWebView 使用时需要

1
#import <WebKit/WebKit>

它的使用更加的方便,功能更加的齐全,所以逐步的取代了 UIWebView。

加载方法

加载网页

同 UIWebView 的方法相同

1
2
3
NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
NSURLRequest *request =[NSURLRequest requestWithURL:url];
[self.webview loadRequest:request];

加载本地文件

加载本地文件的时候不再采用

1
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;

这个方法,而是采用了新的方法:

1
2
NSURL *url = [NSURL fileURLWithPath:@"/Users/ios/Desktop/图片/xxx.jpg"];
[webView loadFileURL:url allowingReadAccessToURL:url];

使用

1
-(nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessUR;

这个方法加载本地文件,不会调用发送网络请求的代理。

一些用于网络加载的方法

1
2
3
- (WKNavigation *)loadRequest:(NSURLRequest *)request;
- (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
- (WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL;

同网页刷新有关的函数

1
2
3
4
5
6
7
8
@property (nonatomic, readonly) BOOL canGoBack;
@property (nonatomic, readonly) BOOL canGoForward;
- (WKNavigation *)goBack;
- (WKNavigation *)goForward;
- (WKNavigation *)reload;
- (WKNavigation *)reloadFromOrigin; // 增加的函数
- (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item; // 增加的函数
- (void)stopLoading;
  1. reloadFromOrigin 会比较网络数据是否有变化,没有变化则使用缓存,否则从新请求。
  2. goToBackForwardListItem:比向前向后更强大,可以跳转到某个指定历史页面

常用的属性

  1. allowsBackForwardNavigationGestures:BOO L类型,是否允许左右划手势导航,默认不允许
  2. estimatedProgress:加载进度,取值范围 0~1
  3. title:页面 title
  4. scrollView.scrollEnabled:是否允许上下滚动,默认允许
  5. backForwardList:WKBackForwardList 类型,访问历史列表,可以通过前进后退按钮访问,或者通过 goToBackForwardListItem 函数跳到指定页面

代理协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#pragma mark - WKNavigationDelegate 代理方法

/**
* 每当加载一个请求之前会调用该方法,通过该方法决定是否允许或取消请求的发送
*
* @param navigationAction 导航动作对象
* @param decisionHandler 请求处理的决定
*/

- (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void(^)(WKNavigationActionPolicy))decisionHandler{
NSLog(@"每当加载一个请求之前会调用该方法,通过该方法决定是否允许或取消请求的发送");
// 获得协议头(可以自定义协议头,根据协议头判断是否要执行跳转)
NSString *scheme = navigationAction.request.URL.scheme;
if ([scheme isEqualToString:@"itheima"]) {
// decisionHandler 对请求处理回调
//WKNavigationActionPolicyCancel:取消请求
//WKNavigationActionPolicyAllow:允许请求
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}

/**
* 当开始发送请求时调用
*/
- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"当开始发送请求时调用");
}
/**
* 当请求过程中出现错误时调用
*/
- (void)webView:(WKWebView*)webView didFailNavigation:(WKNavigation*)navigation withError:(NSError *)error {
NSLog(@"当请求过程中出现错误时调用");
}

/**
* 当开始发送请求时出错调用
*/
- (void)webView:(WKWebView*)webView didFailProvisionalNavigation:(WKNavigation*)navigation withError:(NSError *)error {
NSLog(@"当开始发送请求时出错调用");
}

/**
* 每当接收到服务器返回的数据时调用,通过该方法可以决定是否允许或取消导航
*/
- (void)webView:(WKWebView*)webView decidePolicyForNavigationResponse:(WKNavigationResponse*)navigationResponse decisionHandler:(void(^)(WKNavigationResponsePolicy))decisionHandler{
NSLog(@"每当接收到服务器返回的数据时调用,通过该方法可以决定是否允许或取消导航");
//decisionHandler 对响应的处理
//WKNavigationActionPolicyCancel:取消响应
//WKNavigationActionPolicyAllow:允许响应
decisionHandler(WKNavigationResponsePolicyAllow);
}

/**
* 当收到服务器返回的受保护空间(证书)时调用
* @param challenge 安全质询-->包含受保护空间和证书
* @param completionHandler 完成回调-->告诉服务器如何处置证书
*/
- (void)webView:(WKWebView*)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullablecredential))completionHandler {
NSLog(@"当收到服务器返回的受保护空间(证书)时调用");
// 创建凭据对象
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 告诉服务器信任证书
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}

/**
* 当网页加载完毕时调用:该方法使用最频繁
*/
- (void)webView:(WKWebView*)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
NSLog(@"当网页加载完毕时调用:该方法使用最频繁");
[webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@",result);
}];
}

WKWebKit 与 JS 交互

OC 调用 JS

1
2
3
[webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@",result);
}];

运行时加载 JS 代码

1
2
3
4
5
6
7
8
9
10
11
12
NSString *js = @"window.alert('欢迎体验WkWebView!');";
//初始化WKUserScript对象
//WKUserScriptInjectionTimeAtDocumentStart 为网页加载完成时注入
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
//根据生成的WKUserScript对象,初始化WKWebViewConfiguration
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addUserScript:script];
WKWebView * webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]]];
webView.UIDelegate = self;
webView.navigationDelegate = self;
[self.view addSubview:webView];
1
2
3
4
5
6
7
8
//在JS端调用alert函数时,会触发此代理方法。
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;

//JS端调用confirm函数时,会触发此代理方法。
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;

//JS端调用prompt函数时,会触发此代理方法。
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;

上面的3个方法需要在方法需要调用

1
completionHandler();

JS 调用 OC

在 UIWebView 中 JS 调用 OC 可以采用拦截网络请求的方式,当然在 WKWebView 中也可以采用这种方式。但是 WKWebView 提供了一种新特性,叫做 MessageHandler,用它可以很方便的实现 JS 对 OC 的调用。使用方式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addScriptMessageHandler:self name:@"jsMethod"];
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
[self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];

self.webView.navigationDelegate = self;
self.webView.UIDelegate = self;
[self.view addSubview:self.webView];

/**
*注意addScriptMessageHandler 容易引起循环引用,所以要在适当的地方调用 - (void)removeScriptMessageHandlerForName:(NSString *)name;方法进行移除
*/

实现 WKScriptMessageHandler 的协议方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
// message.body -- Allowed types are NSNumber, NSString, NSDate, NSArray,NSDictionary, and NSNull.
NSLog(@"body:%@",message.body);
if ([message.name isEqualToString:@"jsMethod"]) {
[self js_Method:message.body];
}
}

- (void)js_Method:(NSDictionary *)params{
if (params && [params isKindOfClass:[NSDictionary class]]) {
NSLog(@"%@",params);
}
}

实现这种调用的 JS 代码为

1
window.webkit.messageHandlers.jsMethod.postMessage({longitude:1.00,latitude:20});

iOS UIWebView

发表于 2018-10-18

UIWebView 是用来加载网页数据的一个框架。可以用来加载 pdf、word、txt 等文件。

UIWebView 加载函数

1
2
3
- (void)loadRequest:(NSURLRequest *)request;
- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;

加载网页

1
2
3
4
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height - 20)];
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
[webView loadRequest:request];
[self.view addSubview:webView];

代理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 是否允许加载网页,也可获取 JS 要打开的 url,通过截取此 url 可与 JS 交互
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(@"是否允许加载网页");
return YES;
}

// 开始加载网页
- (void)webViewDidStartLoad:(UIWebView *)webView {
NSLog(@"开始加载网页");
}

// 网页加载完成
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(@"网页加载完成");
}

// 网页加载错误
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSLog(@"网页加载错误时调用");
}

OC 与 JS 交互

OC 调用 JS

OC 调用 JS 是通过下面的方法来实现的

1
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

为方法传入的参数 script 是将要执行的 JS 代码块,可以接收 NSString 类型的返回值。
例如,打印网页的标题,方法如下:

1
NSLog(@"%@",[webView stringByEvaluatingJavaScriptFromString:@"document.title"]);

JS 调用 OC

JS 是不可以调用 OC 的,但是 JS 可以将需要执行的OC操作封装到网络请求之中,然后由 OC 来拦截这个请求,获取 url 字符串进行解析,实现变相的调用。这种调用主要依赖的代理方法是:

1
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

当在 webView 中将要发起的网络请求的 url 写成 ios://js_sendMessage?title=111&detail=222 这个样子的时候,可以采用下面的方式在 APP 中截获请求,并进行相应的处理来调用本地的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString * url = request.URL.absoluteString;
NSString * scheme = @"ios://";
if ([url hasPrefix:scheme]) {

NSString * path = [url substringFromIndex:scheme.length];
NSArray * subPaths = [path componentsSeparatedByString:@"?"];
NSString * methodName = [NSString stringWithFormat:@"%@:",[subPaths firstObject]];
NSArray * params = nil;
if (subPaths.count == 2) {
params = [[subPaths lastObject] componentsSeparatedByString:@"&"];
}
NSMutableDictionary * dic = [NSMutableDictionary dictionary];
for (NSString * str in params) {
NSArray * arr = [str componentsSeparatedByString:@"="];
NSString * key = arr[0];
NSString * value = arr[1];
[dic setValue:value forKey:key];
}
if ([self respondsToSelector:NSSelectorFromString(methodName)] ) {
[self performSelector:NSSelectorFromString(methodName) withObject:dic];
}
return NO;

}
return YES;
}

- (void)js_sendMessage:(id)message{
NSDictionary * dic = message;
NSLog(@"%@",dic[@"title"]);
}

这样就实现了 JS 对 OC 的方法调用。

layoutSubViews、setNeedsLayout 和 layoutIfNeeded

发表于 2018-10-17

layoutIfNeeded 可能的实现机制

1
2
3
4
5
6
7
8
9
10
-(void)layoutIfNeeded {
if (self._needsLayout) {
UIView *sv = self.superview;
if (sv._needsLayout) {
[sv layoutIfNeeded];
} else {
[self layoutSubviews];
}
}
}

其中 _needsLayout 由 setNeedsLayout 进行设置。

使用示例

现在自定义一个 UICustomView 继承自 UIView, 这个 UICustomView 中包含一个 contentView 属性和一个 edgeInsets 属性来控制contentView 的 frame。
这个自定义的 UICustomView 如下:

1
2
3
4
5
6
#import <UIKit/UIKit.h>

@interface UICustomView : UIView
@property (nonatomic, strong) UIView * contentView;
@property (nonatomic, assign) UIEdgeInsets edgeInsets;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#import "UICustomView.h"

@implementation UICustomView

- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
_contentView = [[UIView alloc] initWithFrame:self.bounds];
[self addSubview:_contentView];
}
return self;
}

- (void)layoutSubviews{
[super layoutSubviews];
self.contentView.frame = CGRectMake(
self.bounds.origin.x + self.edgeInsets.left,
self.bounds.origin.y + self.edgeInsets.top,
self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom);

NSLog(@"调用===layoutSubviews %@",self);
}

- (void)setEdgeInsets:(UIEdgeInsets)edgeInsets{
_edgeInsets = edgeInsets;
[self setNeedsLayout];
[self layoutIfNeeded];
}

上面的 UICustomView 重写了 layoutSubViews 方法,并且实现了当 edgeInsets 的值发生改变,contentView 的 frame 随及发生改变。

当执行下面的动画:

1
2
3
[UIView animateWithDuration:2 animations:^{
self.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

contentView 动态变小,如果 setEdgeInsets: 方法中不调用 layoutIfNeeded,那么 contentView 的变小不会在 animate 的 block 中,而是在 block 外,也就动态变小。如果 setEdgeInsets: 方法中 只调用 layoutIfNeeded,那么 contentView 的大小不会改变,因为此时 setNeedsLayout 并没有标记。

参考:What is the relationship between UIView’s setNeedsLayout, layoutIfNeeded and layoutSubviews?

iOS 中 CGRectInset & CGRectOffset

发表于 2018-10-17

CGRectInset

CGRect CGRectInset(CGRect rect, CGFloat dx, CGFloat dy)
表示在 rect 的基础上,分别在 x 和 y 方向上进行缩放:

  1. dx > 0, 在 x 方向上缩小 2 dx; dx < 0, 在 x 方向上放大 2 (-dx);

  2. dy > 0, 在 y 方向上缩小 2 dy; dy < 0, 在 y 方向上放大 2 (-dy);

1
2
3
4
5
6
7
8
UIView * view1 = [[UIView    alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = [UIColor redColor];
[self.view addSubview:view1];

UIView * view2 = [UIView new];
view2.frame = CGRectInset(view1.frame, 10, 10);
view2.backgroundColor = [UIColor blackColor];
[self.view addSubview:view2];

如下图:

CGRectOffset

CGRect CGRectOffset(CGRect rect, CGFloat dx, CGFloat dy) 表示以 rect 的 origin 为基点在 x 和 y 方向进行偏移:

  1. dx > 0, 在 x 方向上向右偏移 dx; dx < 0, 在 x 方向上向左偏移 -dx;
  2. dy > 0, 在 y 方向上向下偏移 dy; dy < 0, 在 y 方向上向上偏移 -dy;
1
2
3
4
5
6
7
8
UIView * view1 = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = [UIColor redColor];
[self.view addSubview:view1];

UIView * view2 = [UIView new];
view2.frame = CGRectOffset(view1.frame, -20, 20);
view2.backgroundColor = [UIColor blackColor];
[self.view addSubview:view2];

如下图:

UIView 的 layoutSubviews

发表于 2018-10-16

在 iOS 开发中,应用的界面可以由一棵树状的 UIView 组成。这颗树的根是 UIWindow,接下来是 UIWindow 的 rootViewController 的 根 UIView, 在这个 UIView 上 可以有各种各样的子 UIView。

UIView 提供了叫做 layoutSubviews 的方法来实现父 UIView 对子 UIView 的布局管理。

在程序开发中并不会直接调用layoutSubViews的方法,而是在一些时机自动触发:

  1. init & initWithFrame 方法都不会触发 layoutSubviews
  2. addSubview 方法会自动触发 layoutSubViews
  3. 对UIView 的 frame 进行设置(这个frame发生了变化),会触发 layoutSubViews
  4. 滚动 UIScrollView 会触发它的 layoutSubViews
  5. 改变 UIView 的 frame 也会触发它的父 UIView 的 layoutSubViews
  6. 旋转 Screen 可以触发父 UIView 上的 layoutSubViews ??
  7. 调用 setNeedsLayout 非立即触发 layoutSubViews
  8. 调用 layoutIfNeeded 立即触发 layoutSubViews

sizeToFit 和 sizeThatFits

发表于 2018-10-12

- (void)sizeToFit;

调用这个方法会改变当前 view 的 bounds.size。

- (CGSize)sizeThatFits:(CGSize)size;

返回一个适合给定 size 的最佳 size,默认是返回当前的 size。

当 UIView 调用 sizeToFit 的时候,会调用 sizeThatFits 方法来计算 UIView 的 bounds.size 然后改变 frame.size。

了解 UIButton 的 UIEdgeInsets 属性

发表于 2018-09-26

在应用开发时,经常会用到 UIButton 这个控件。在UI的样式上,它支持仅图片、仅文字、图片+文字(默认的左图右文形式)。但是在一些场景下常常需要对原生的 UIButton 进行一些调整。例如:调整图片和文字的距离、上图下文等形式。这个时候就要借助于 UIButton 的 UIEdgeInsets 属性了。然而,这个属性的过程中,常常会遇到设置不生效的情况。这是因为对这个属性了解的还不够,本文就是针对 UIEdgeInsets 的一些基本的使用说明。

UIEdgeInsets 的种类

这里所说的 UIEdgeInsets 仅仅是针对 UIButton 的。

1
2
3
typedef struct UIEdgeInsets {
CGFloat top, left, bottom, right;
} UIEdgeInsets;

结构体,参数分别表示距离上、左、下、右四个边界的距离,它的默认值为0.

UIButton 中的 UIEdgeInsets 属性主要有三个:

1. contentEdgeInsets

这个属性是 UILabel + UIImageView 整体的偏移。如果要将 button 中的内容整体偏移15。写法如下

1
button.contentEdgeInsets = UIEdgeInsetsMake(15, 15, -15, -15);

2. titleEdgeInsets

这个代表的是 title 的 UIEdgeInsets,其中的 top、bottom、right 是相对于外层的 contentView 的,而 left 是相对于 image 来说的。

3. imageEdgeInsets

这个代表的是 image 的 UIEdgeInsets,其中的top、bottom、left 是相对于外层的 contentView 的,而 right 是相对于 title 来说的。

造成 imageEdgeInsets 和 titleEdgeInsets 出现这种情况的原因是,UIButton 默认的图文按钮是左图右文的形式。

在上图中 top、left、bottom、right 在沿着箭头方向上的位移为正值,逆着箭头方向的值为负值。

UIEdgeInsets 的简单应用

实现左文右图

调整 imageEdgeInsets 向右偏移 title 的宽度,titleEdgeInsets 向左偏移 image 的宽度,形成如下的按钮:

1
2
3
4
CGRect rectTitle = button.titleLabel.frame;
CGRect rectImage = button.imageView.frame;
button.titleEdgeInsets = UIEdgeInsetsMake(0, - rectImage.size.width, 0, rectImage.size.width);
button.imageEdgeInsets = UIEdgeInsetsMake(0, rectTitle.size.width, 0, - rectTitle.size.width);

实现上图下文

调整 imageEdgeInsets 向右偏移 title 宽度的一半,向上偏移 title 高度的一半;titleEdgeInsets 向左偏移 image 宽度的一半,向下偏移 image 高度的一半。形成如下的按钮:

1
2
3
4
CGRect rectTitle = button.titleLabel.frame;
CGRect rectImage = button.imageView.frame;
button.titleEdgeInsets = UIEdgeInsetsMake(rectImage.size.height / 2, - rectImage.size.width/2, -rectImage.size.height / 2 , rectImage.size.width/2);
button.imageEdgeInsets = UIEdgeInsetsMake(- rectTitle.size.height / 2, rectTitle.size.width/2, rectTitle.size.height / 2, -rectTitle.size.width/2);

UIEdgeInsets 的简单分析

在上面的应用中可以发现t op = - bottom、left = - right,这时候的位移值直接从 top 和 left 的值就可以看出,但是当 top != - bottom, left != - right 时,实际的偏移量就不是很直观了。而且如果随意取值的话往往会出现一些非正常的效果,所以尽量比较规范的使用 UIEdgeInsets。

UITableView Section 之间有间隔

发表于 2018-08-21

最近碰到了一个问题:UITableView 的 style UITableViewStyleGrouped 的时候,各个section 之间会存在间隔,虽然已经实现了代理方法中对section header 和 section footer的相关设置,但是似乎没有起什么作用,后来就使用了下面的方法:

1
2
tableView.sectionHeaderHeight = 0;
tableView.sectionFooterHeight = 0;

然后就生效了,不知道是什么原因…

123

汤圆红豆酥

22 日志
© 2019 汤圆红豆酥
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4