iOS 动态对UIImageView加载到的图片进行偏移裁剪处理

devtools/2024/12/23 7:21:57/

关键点:图片偏移重绘、图片重绘时机处理
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

补充说明:

说明

  1.  利用swizzle方法交换setImage 和layoutSubviews方法实现,它们将风别走向MY_setImage:和方法My_layoutSubviews从中获取到imgOriginal之后,再判断是否要进行重绘(needImageRedrawJudge),如果要进行重绘,重绘之后得到图片对象  destImg|imgOffset,这时候调用MY_setImage:方法,根据方法交换的逻辑,实际上就是调用了UIImageView的setImage:方法,从而实现拦截图片,并判断和重回图片在设置最终图片的效果。
  2.  至于图片重绘的具体逻辑,可根据具体需求进行灵活变换,此处重在说明如何判断需求和在拦截,以及拦截使用的方法,核心设计知识领域有:宏代码的编程简化、运行时方法、图片重绘

http://www.ppmy.cn/devtools/2190.html

相关文章

【Hello算法】 > 第 2 关 >数据结构 之 数组与链表

数据结构 之 数组与链表 1&#xff1a;Understanding data structures &#xff01;——了解数据结构——1.1&#xff1a;Classification-分类-1.2&#xff1a;Type-类型- 2&#xff1a;Arrays are the bricks that make up the wall of data structures *——数组是组成数据结…

OpenHarmony实战开发-如何利用panel实现底部面板内嵌套列表。

介绍 本示例主要介绍了利用panel实现底部面板内嵌套列表&#xff0c;分阶段滑动效果场景。 效果图预览 使用说明 点击底部“展开”&#xff0c;弹出panel面板。在panel半展开时&#xff0c;手指向上滑动panel高度充满页面&#xff0c;手指向下滑动panel隐藏。在panel完全展开…

python入门(一)配置环境和选择IDE

Python&#xff0c;作为一种简洁易懂的编程语言&#xff0c;近年来在全球范围内受到了广泛的关注和追捧。它不仅语法简单明了&#xff0c;易于上手&#xff0c;而且拥有强大的第三方库和广泛的应用领域。从数据分析、机器学习到Web开发&#xff0c;Python都能发挥出色的性能&am…

以ue独立游戏为主攻

想到哪里写到哪里吧。 我决策错误了。原以为目前工作不好找&#xff0c;放松了对ue的学习。其实是错误的。 前两天&#xff0c;以前的同事内推&#xff0c;月薪三万五可能的原始股&#xff0c;面试也通过了。考虑到是小公司&#xff0c;坑也比较多&#xff0c;专业知识不懂&am…

oracle guid 使用

在Oracle数据库中&#xff0c;全局唯一标识符&#xff08;GUID&#xff09;通常用于生成一个唯一的标识符&#xff0c;这个标识符可以在整个系统中唯一地标识一个实体或记录。在Oracle中&#xff0c;GUID通常通过RAW或VARCHAR2类型来表示。Oracle本身并没有像其他数据库系统&am…

在Windows下C++使用vcpkg配置crow环境

环境搭建 首先需要配置vcpkg环境&#xff0c;可以参考链接 vcpkg install crow安装crow 代码 // main.cpp #include <crow.h>int main() {crow::SimpleApp app;CROW_ROUTE(app, "/")([]{return "Hello, World!";});app.port(8080).multithreaded(…

antd3.x Tree组件遇到的坑

在使用antd 3.x低版本组件的过程中&#xff0c;使用Tree组件&#xff0c;加载树形数据时候&#xff0c;第一次始终无法加载数据&#xff0c;在查阅antd文档后发现Tree组件会缓存数据&#xff0c;需要进行判断数据是否已加载 {microFolderList.length ?<TreedefaultExpanded…

Jupyter Notebook

Jupyter Notebook介绍 一、概述 Jupyter Notebook是一个开源的Web应用程序&#xff0c;可让用户创建和共享包含实时代码、方程、可视化和叙述性文本的文档。其名称源自Jupyter项目&#xff08;即Ju&#xff08;Julia&#xff09;、Py&#xff08;Python&#xff09;和R的组合…