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

关键点:图片偏移重绘、图片重绘时机处理

  1. 图片偏移重绘问题
  • 原因:UIImageView的现有填充模式已经不能满足应用需求,需自定义偏移量结合填充模式实现效果
  • 方法:按需求样式重绘,设置偏移量比例,偏移阈值,对图片进行裁剪。
  1. 重绘时机问题
  • 选择:UIImageView自带的setImage方法,这是获取图片和进行重绘渲染的关口,从这里处理最恰当
  • 方法:利用iOS底层方法,在运行时(runtime)进行方法交换,属性绑定等。
  1. 补充
  • 因为运行时编程代码复杂冗余多,可以考虑运用第三方库方法解决这个问题,此处选择德国软件工程师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. 至于图片重绘的具体逻辑,可根据具体需求进行灵活变换,此处重在说明如何判断需求和在拦截,以及拦截使用的方法,核心设计知识领域有:宏代码的编程简化、运行时方法、图片重绘
相关推荐
若水无华1 天前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
Aress"1 天前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy1 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克1 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨1 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆2 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂2 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T3 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20253 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz3 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频