Objective-C 背景拉伸
Objective-C 背景拉伸
背景拉伸的问题
考察如下添加按钮的代码:
- (void)loadView {
[super loadView];
UIButton *myBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[myBtn setContentEdgeInsets:UIEdgeInsetsMake(10, 50, 10, 50)];
[myBtn setTitle:@"click me" forState:UIControlStateNormal];
[myBtn setBackgroundImage:[UIImage imageNamed:@"purple_button"]
forState:UIControlStateNormal];
[self.view addSubview:myBtn];
// ...
}
得到的结果:
圆角被拉伸的效果
这里图片作为背景时,默认被拉伸,造成了图片圆角的变形。
背景平铺
也有不变形的情况,使用图片创建 UIColor 作为背景。但此时背景图片是按原大小平铺开的,代码及效果如下:
UIColor *bgColor =
[UIColor colorWithPatternImage:[UIImage imageNamed:@"purple_button"]];
[myBtn setBackgroundColor:bgColor];
平铺的背景
这种适用用来创建纯色背景,比如用一张 1X1 的图片。(为啥不直接使用 UIColor 的纯色?)
精准拉伸
像开头的那种情况,其实只需要拉伸图片的中间部分,保持四个角落即可。
原生 API 中确实也有相应的方法提供支持,那就是 resizableImageWithCapInsets:
。
通过它可以指定图片四个角落的区域,这四个指定出来的区域在拉伸时不受影响。
保留四个角的示意图
所以修正后的圆角可以这样来实现:
UIImage *bgImage = [UIImage imageNamed:@"purple_button"];
UIImage *resizedImage =
[bgImage resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
[myBtn setBackgroundImage:resizedImage forState:UIControlStateNormal];
修正后的效果:
修正后的圆角
该方法比较常用的地方还有就是聊天窗口的背景,将聊天气泡四个角保护起来,内部任意拉伸,就可以展示任意聊天文本了。
聊天气泡背景
单个方向上的拉伸
虽然 resizableImageWithCapInsets:
很有用,但也只解决了部分场景。
请看如下的 tab bar 展现:
带弧形的 tab bar 背景,图片来自 flutter 文档
因为横向宽度在不同屏幕下是不一样的,很自然地,这里的效果,我们只需要如下图片即可:
tab bar 背景
仔细一想,这里的场景需要拉伸的是两边,而不是图片的中间。所以 resizableImageWithCapInsets:
在这里派不上用场,需要自行解决。
我们可以在 UIImage 上扩展一个方法,来解决这里只拉伸两边的需求。
以下实现来自 StackOverflow Klass 的回答:
- (UIImage *)pbResizedImageWithWidth:(CGFloat)newWidth andTiledAreaFrom:(CGFloat)from1 to:(CGFloat)to1 andFrom:(CGFloat)from2 to:(CGFloat)to2 {
NSAssert(self.size.width < newWidth, @"Cannot scale NewWidth %f > self.size.width %f", newWidth, self.size.width);
CGFloat originalWidth = self.size.width;
CGFloat tiledAreaWidth = (newWidth - originalWidth)/2;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(originalWidth + tiledAreaWidth, self.size.height), NO, self.scale);
UIImage *firstResizable = [self resizableImageWithCapInsets:UIEdgeInsetsMake(0, from1, 0, originalWidth - to1) resizingMode:UIImageResizingModeTile];
[firstResizable drawInRect:CGRectMake(0, 0, originalWidth + tiledAreaWidth, self.size.height)];
UIImage *leftPart = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(CGSizeMake(newWidth, self.size.height), NO, self.scale);
UIImage *secondResizable = [leftPart resizableImageWithCapInsets:UIEdgeInsetsMake(0, from2 + tiledAreaWidth, 0, originalWidth - to2) resizingMode:UIImageResizingModeTile];
[secondResizable drawInRect:CGRectMake(0, 0, newWidth, self.size.height)];
UIImage *fullImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return fullImage;
}
类似 resizableImageWithCapInsets:
,这里扩展的方法也会返回一张拉伸好的新图片。不同之处在于,指定好两边需要拉伸的部分后,得到的是只拉伸两边而保留了中间的图片。