iOS 动态对UIImageView加载到的图片进行偏移裁剪处理
关键点:图片偏移重绘、图片重绘时机处理
1. 图片偏移重绘问题
- 原因:UIImageView的现有填充模式已经不能满足应用需求,需自定义偏移量结合填充模式实现效果
- 方法:按需求样式重绘,设置偏移量比例,偏移阈值,对图片进行裁剪。
2. 重绘时机问题
- 选择:UIImageView自带的setImage方法,这是获取图片和进行重绘渲染的关口,从这里处理最恰当
- 方法:利用iOS底层方法,在运行时(runtime)进行方法交换,属性绑定等。
3. 补充
- 因为运行时编程代码复杂冗余多,可以考虑运用第三方库方法解决这个问题,此处选择德国软件工程师Philipp Rentzsch开发的一个开源库[JRSwizzle](https://github.com/rentzsch/jrswizzle)辅助完成。
- 使用cocoapod集成JRSwizzle库
核心代码:
- 宏定义再次简化运行时代码
#import <JRSwizzle/JRSwizzle.h>
/* 方法交换宏*/
#define MYSwizzle(prefix, SEL) \
do { \
NSError *error = nil; \
[[self class] jr_swizzleMethod: @selector(SEL) \
withMethod: @selector(prefix##_##SEL)error:&error]; \
if (error) {NSLogError(@ "%@ swizzle %@ with %@ failed.", \
NSStringFromClass(self), \
NSStringFromSelector(@selector(SEL)), \
NSStringFromSelector(@selector(prefix##_##SEL)));} \
} while (0)
/*添加属性宏*/
#define MY_ASSOCIATE_OBJECT(_getter_, _setter_, _association_, _type_) \
- (void)_setter_: (_type_)object { \
[self willChangeValueForKey: @#_getter_]; \
objc_setAssociatedObject(self, _cmd, object, OBJC_ASSOCIATION_##_association_); \
[self didChangeValueForKey: @#_getter_]; \
} \
-(_type_)_getter_ { \
return objc_getAssociatedObject(self, @selector(_setter_:)); \
}
- 定义好红代码之后,写一个UIImageView的分类方法,记录加载原始图片imgOriginal,以及便宜设置类MYImaeViewOffset;
- 首先定义偏移重绘配置类MYImaeViewOffset:
/**
* 用于判断和控制UIImageView设置图片时候的偏移量的数据类。
*
* ratioOffsetX x轴方向的偏移量比例.
* ratioOffsetY y轴方向的偏移量比例.
*
* ratioThreshold 进行偏移重绘的阈值. 实值为 imgv_w / imgv_h.
* 如果设定ratioThreshold = 1.8, 而当前UIImageView.size = {100, 50}
* 则实值为 2。此时超过偏移阈值,需对图片进行偏移裁剪重绘。
*
* @note ignoreThreshold 注意如果此值为YES,则忽略阈值,需要重绘。
*/
typedef NS_OPTIONS(NSUInteger, MYImageViewAlignmentMask) {
MYImageViewAlignmentMaskCenter = 1 << 0,
MYImageViewAlignmentMaskLeft = 1 << 1,
MYImageViewAlignmentMaskRight = 1 << 2,
MYImageViewAlignmentMaskTop = 1 << 3,
MYImageViewAlignmentMaskBottom = 1 << 4,
};
@interface MYImageViewOffset : NSObject
@property (nonatomic,assign) CGFloat ratioOffsetX;
@property (nonatomic,assign) CGFloat ratioOffsetY;
@property (nonatomic,assign) CGFloat ratioThreshold;//阈值
@property (nonatomic,assign) BOOL ignoreThreshold;//忽略阈值(默认阈值1.3)长图w/h >= 1.3
@property (nonatomic,assign) CGFloat cornerRadius;//圆角半径
@property (nonatomic,assign) MYImageViewAlignmentMask alignment;//对齐方式。
+ (instancetype)offsetRatio:(CGFloat)ratioX ratioY:(CGFloat)ratioY;
+ (instancetype)offsetRatio:(CGFloat)ratioX ratioY:(CGFloat)ratioY cornerRadius:(CGFloat)cornerRadius;
+ (instancetype)offsetRatio:(CGFloat)ratioX ratioY:(CGFloat)ratioY cornerRadius:(CGFloat)cornerRadius align:(MYImageViewAlignmentMask)aligment;
@end
@implementation MYImageViewOffset
+ (instancetype)offsetRatio:(CGFloat)ratioX ratioY:(CGFloat)ratioY {
return [MYImageViewOffset offsetRatio:ratioX ratioY:ratioY cornerRadius:0];
}
+ (instancetype)offsetRatio:(CGFloat)ratioX ratioY:(CGFloat)ratioY cornerRadius:(CGFloat)cornerRadius {
MYImageViewOffset *offset = [MYImageViewOffset offsetRatio:ratioX ratioY:ratioY cornerRadius:cornerRadius align:MYImageViewAlignmentMaskCenter];
return offset;
}
+ (instancetype)offsetRatio:(CGFloat)ratioX ratioY:(CGFloat)ratioY cornerRadius:(CGFloat)cornerRadius align:(MYImageViewAlignmentMask)aligment {
MYImageViewOffset *offset = [MYImageViewOffset new];
offset.ratioOffsetX = ratioX;
offset.ratioOffsetY = ratioY;
offset.ratioThreshold = 1.3;//默认阈值。
offset.cornerRadius = cornerRadius;
offset.alignment = aligment;
return offset;
}
@end
- 然后实现UIImageVIew分类方法:
@interface UIImageView (ImageOffset)
@property (nonatomic, strong) UIImage *imgOriginal;
@property (nonatomic, strong) MYImageViewOffset *offset;
- (void)reloadOffsetJudge;
@end
@implementation UIImageView (ImageOffset)
MY_ASSOCIATE_OBJECT(offset, setOffset, RETAIN, MYImageViewOffset *);
MY_ASSOCIATE_OBJECT(imgOriginal, setImgOriginal, RETAIN, UIImage *);
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
MYSwizzle(MY, setImage:);
MYSwizzle(MY, layoutSubviews);
});
}
- (void)MY_layoutSubviews {
if ([self needImageRedrawJudge]) {
UIImage *imgOffset = [self offsetImage:self.imgOriginal];
if (imgOffset) {
[self MY_setImage:imgOffset];
}
}
[self MY_layoutSubviews];
}
- (void)MY_setImage:(UIImage *)image {
[self setImgOriginal:image];
UIImage *destImg = nil;
if ([self needImageRedrawJudge] && image) {
destImg = [self offsetImage:image];
}else{
destImg = image;
}
[self MY_setImage:destImg];
}
- (void)reloadOffsetJudge {
if ([self needImageRedrawJudge]) {
NSLogInfo(@"-- layout re");
UIImage *imgOffset = [self offsetImage:self.imgOriginal];
if (imgOffset) {
[self MY_setImage:imgOffset];
}
}
}
- (UIImage *)offsetImage:(UIImage *)imgOri {
if (!imgOri) return nil;
CGFloat offX = self.offset.ratioOffsetX * imgOri.size.width;
CGFloat offY = self.offset.ratioOffsetY * imgOri.size.height;
CGRect cutRect = CGRectMake(offX, offY, imgOri.size.width, imgOri.size.height);
UIImage *newImage = [self image:imgOri cropToRect:cutRect cornerRadius:self.offset.cornerRadius];
return newImage;
}
- (BOOL)needImageRedrawJudge {
if(self.offset.cornerRadius){
return YES;
}
CGSize imgSize = self.imgOriginal.size;
if (self.width <= 0 || self.height <= 0) return NO;
if (imgSize.width / imgSize.height >= 1){
//如果图片本身是一张横图(w>=h)则不需进行重绘
return NO;
}
if (self.offset) {
CGFloat ratioThreshold = self.width / self.height;
if (ratioThreshold > self.offset.ratioThreshold) {
//图片视图控件的阈值判断(视图控件UIImageView是一个通过了阈值的横框,方可进行重绘。)
return YES;
}
}
return NO;
}
/*
图片剪裁接口
* 1.rect超过image.size则返回原图
* 2.rect可以是size的任何一个子集,比如取中间半区域 CG
*/
- (UIImage *)image:(UIImage *)imgOri cropToRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius{
rect.origin.x *= imgOri.scale;
rect.origin.y *= imgOri.scale;
rect.size.width *= imgOri.scale;
rect.size.height *= imgOri.scale;
UIImage *outputImage = imgOri;
if(rect.size.width > 0 && rect.size.height > 0){
CGImageRef imageRef = CGImageCreateWithImageInRect(imgOri.CGImage, rect);
UIImage *image = [UIImage imageWithCGImage:imageRef scale:imgOri.scale orientation:imgOri.imageOrientation];
outputImage = image;
CGImageRelease(imageRef);
}
if(self.width && self.height){
if(cornerRadius > 0){
CGFloat ratioCR = cornerRadius * (outputImage.size.width / self.width);
UIGraphicsBeginImageContextWithOptions(outputImage.size, NO, outputImage.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect imgRect = CGRectMake(0, 0, outputImage.size.width, outputImage.size.height);
CGContextSaveGState(context);
[[UIBezierPath bezierPathWithRoundedRect:imgRect cornerRadius:ratioCR] addClip];
[outputImage drawInRect:imgRect];
CGContextRestoreGState(context);
UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
outputImage = roundedImage;
}
if(self.offset.alignment != MYImageViewAlignmentMaskCenter){
CGFloat logicH = (outputImage.size.height * self.width)/outputImage.size.width;
CGFloat logicW = (outputImage.size.width * self.height)/outputImage.size.height;
CGFloat offsetH = (self.height - logicH);
CGFloat offsetV = (self.width - logicW);
BOOL needRedraw = NO;
if(offsetH > 0){ //横图(只有上下偏移)
offsetV = 0;
logicW = self.width;
if(self.offset.alignment == MYImageViewAlignmentMaskTop){
offsetH = 0;
needRedraw = YES;
}else if (self.offset.alignment == MYImageViewAlignmentMaskBottom){
needRedraw = YES;
}
}
if (offsetV > 0){ //竖图(只有左右偏移)
logicH = self.height;
offsetH = 0;
if(self.offset.alignment == MYImageViewAlignmentMaskLeft){
offsetV = 0;
needRedraw = YES;
}else if (self.offset.alignment == MYImageViewAlignmentMaskRight){
needRedraw = YES;
}
}
if(needRedraw){
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
[outputImage drawInRect:CGRectMake(offsetV, offsetH, logicW, logicH)];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
outputImage = resizedImage;
}
}
}
return outputImage;
}
@end
补充说明:
说明
- 利用swizzle方法交换setImage 和layoutSubviews方法实现,它们将风别走向MY_setImage:和方法My_layoutSubviews从中获取到imgOriginal之后,再判断是否要进行重绘(needImageRedrawJudge),如果要进行重绘,重绘之后得到图片对象 destImg|imgOffset,这时候调用MY_setImage:方法,根据方法交换的逻辑,实际上就是调用了UIImageView的setImage:方法,从而实现拦截图片,并判断和重回图片在设置最终图片的效果。
- 至于图片重绘的具体逻辑,可根据具体需求进行灵活变换,此处重在说明如何判断需求和在拦截,以及拦截使用的方法,核心设计知识领域有:宏代码的编程简化、运行时方法、图片重绘
原文地址:https://blog.csdn.net/u012812881/article/details/137789661
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!