iOS开发必备干货

把自己以往做的项目中的一些小功能抠出来写在这里,供大家使用,会一直持续更新

UITextView自适应高度

1.KVO

//静态变量的地址可以保证context的独一无二
static void * abc = &abc;

- (void)viewDidLoad {
    [super viewDidLoad];
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(20, 20, 200, 35)];
    [self.view addSubview:textView];
    textView.layer.borderColor = [UIColor lightGrayColor].CGColor;
    textView.layer.borderWidth = 1;
    textView.layer.cornerRadius = 5;
    textView.font = [UIFont systemFontOfSize:20];

    //为textView添加一个观察者,来观察他的contentSize属性的变化
    //context:上下文. 对某一个属性或者方法之类的 跨方法使用时的唯一标识
    [textView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:abc];
}

//当前对象 如果观察到了 某些变化时就会触发下方的方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    //使用context作为判断依据  尽量不要使用keyPath
    if (context == abc) {
        NSLog(@"%@", change);
        CGSize size = [change[@"new"] CGSizeValue];
        UITextView *textView = (UITextView *)object;
        CGRect frame = textView.frame;
        //限制高度 根据打印,5行时 高度136 高度未变化也不更新布局
        if (size.height > 140 || size.height == frame.size.height) {
            return;
        }
        frame.size.height = size.height;
        textView.frame = frame;
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

2.sizeThatFits:

//文本更改时触发高度自适应
- (void)textViewDidChange:(UITextView *)textView {    

    //根据文本调整textView的高度
    CGSize sizeToFit = [textView sizeThatFits:CGSizeMake(textView.frame.size.width-16, MAXFLOAT)];
    
    //判断高度是否变化,未变化不重新布局
    if (sizeToFit.height != textView.frame.size.height) {
        textView.frame = CGRectMake(textView.frame.origin.x, textView.frame.origin.y, textView.frame.size.width, sizeToFit.height);
    }
}

实现二维码扫描的空心遮罩

cropRect为扫描二维码框的Rect

1.CoreGraphics -> CGPath

- (void)setCropRect:(CGRect)cropRect{
    CAShapeLayer *cropLayer = [[CAShapeLayer alloc] init];    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, nil, cropRect);
    CGPathAddRect(path, nil, self.view.bounds);
    
    [cropLayer setFillRule:kCAFillRuleEvenOdd];//生成空心遮罩
    [cropLayer setPath:path];
    CFRelease(path);//CF对象记得手动释放
    [cropLayer setFillColor:[UIColor blackColor].CGColor];
    [cropLayer setOpacity:0.3];
    
    [cropLayer setNeedsDisplay];
    
    [self.view.layer addSublayer:cropLayer];
}

2.UIKit -> UIBezierPath

- (void)setCropRect:(CGRect)cropRect{
    CAShapeLayer *cropLayer = [[CAShapeLayer alloc] init];
    
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.view.bounds cornerRadius:0];
    UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:cropRect];
    [path appendPath:rectPath];

    [cropLayer setFillRule:kCAFillRuleEvenOdd];//生成空心遮罩
    [cropLayer setPath:path.CGPath];
    [cropLayer setFillColor:[UIColor blackColor].CGColor];
    [cropLayer setOpacity:0.3];
    
    [cropLayer setNeedsDisplay];
    
    [self.view.layer addSublayer:cropLayer];
}
空心遮罩.PNG

UIImageView的手势缩放

通过UIScrollViewDelegate中的代理方法来对UIImageView实现简单的缩放功能
附加手势实现:
缩放比例为1(未进行缩放)时,放大倍数
缩放比例大于1(进行过缩放)时,还原倍数

_scrollView.minimumZoomScale = 1.0; // 最小缩放比例 
_scrollView.maximumZoomScale = 6.0; // 最大缩放比例
_imageView.userInteractionEnabled = YES; // 记得打开UIImageView的用户交互,否则手势无法触发
#pragma mark - UIScrollView Delegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return _imageView; // 指定需要缩放的view,否则无法实现缩放功能(可为多个view)
}
#pragma mark - GestureRecogizer Method
- (void)addGestureRecognizer {
    // 双击的 Recognizer
    UITapGestureRecognizer* doubleRecognizer;
    doubleRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
    doubleRecognizer.numberOfTapsRequired = 2; // 双击
    // _imageView添加双击手势
    [_imageView addGestureRecognizer:doubleRecognizer];
}

-(void)doubleTap:(UITapGestureRecognizer*)recognizer
{
    if (_scrollView.zoomScale > 1.0) {//放大过 --> 还原大小
        [UIView animateWithDuration:.2 animations:^{
            _scrollView.zoomScale = 1.0;
        }];
    } else {//未放大 --> 放大2倍
        [UIView animateWithDuration:.2 animations:^{
            _scrollView.zoomScale = 2.0;
        }];
    }
}
手势缩放.gif

JSON与JSON字符串相互转换

1.JSON转JSON字符串
思路:NSDictionary/NSArray -> NSData -> NSString

方法:

+ (NSString *)parseJSONToJSONString:(id)JSON {

    NSError *error;

    NSData *JSONData = [NSJSONSerialization dataWithJSONObject:JSON options:NSJSONWritingPrettyPrinted error:&error];

    NSString *JSONString = [[NSString alloc] initWithData:JSONData encoding:NSUTF8StringEncoding];

    return JSONString;

}

2.JSON字符串转JSON
思路:NSString -> NSData -> NSDictionary/NSArray

方法:

+ (id)parseJSONStringToJSON:(NSString *)JSONString {

    NSData *JSONData = [JSONString dataUsingEncoding:NSUTF8StringEncoding];

    id JSON = [NSJSONSerialization JSONObjectWithData:JSONData options:NSJSONReadingMutableLeaves error:nil];

    return JSON;
}

备注:
1.NSJSONReadingOptions

typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
    NSJSONReadingMutableContainers = (1UL << 0),
    NSJSONReadingMutableLeaves = (1UL << 1),
    NSJSONReadingAllowFragments = (1UL << 2)
} API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
NSJSONReadingMutableContainers // 返回可变容器,NSMutableDictionary或NSMutableArray
NSJSONReadingMutableLeaves // 不仅返回的最外层是可变的, 内部的子数值或字典也是可变对象
NSJSONReadingAllowFragments // 返回允许JSON字符串最外层既不是NSArray也不是NSDictionary,但必须是有效的JSON Fragment.

2.NSJSONWritingOptions

typedef NS_OPTIONS(NSUInteger, NSJSONWritingOptions) {
    NSJSONWritingPrettyPrinted = (1UL << 0),
    NSJSONWritingSortedKeys API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) = (1UL << 1)
} API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
NSJSONWritingPrettyPrinted // 是将生成的json数据格式化输出,这样可读性高,不设置则输出的json字符串就是一整行
NSJSONWritingSortedKeys // 输出的json字符串就是一整行 仅支持iOS11以上

利用UIBezierPath绘制三角形

- (void)drawRect:(CGRect)rect {
    
    //绘制路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    //设置颜色
    [self.triangleColor set];
    
    //-------------绘制三角形------------
    //
    //                 B
    //                /\
    //               /  \
    //              /    \
    //             /__  __\
    //            A        C
    //
    
    //设置起点 A
    [path moveToPoint:CGPointMake(0, rect.size.height)];
    
    //添加一根线到某个点 B
    [path addLineToPoint:CGPointMake(rect.size.width * 0.5, 0)];
    
    //添加一根线到某个点 C
    [path addLineToPoint:CGPointMake(rect.size.width, rect.size.height)];
    
    //关闭路径
    [path closePath];
    
    //填充(会把颜色填充进去)
    [path fill];
}

验证码倒计时

- (void)theCountdown
{
    __block int timeout = 60;
    __weak __typeof(self)weakSelf = self;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, <#intervalInSeconds#> * NSEC_PER_SEC, <#leewayInSeconds#> * NSEC_PER_SEC); intervalInSeconds(倒计时间隔),leeway(期望的延迟时间)
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    //dispatch_source_set_event_handler(dispatch_source_t source, dispatch_block_t _Nullable handler); dispatch_block_t 为block
    dispatch_source_set_event_handler(timer, ^{
        //因为^{}为block,有循环引用问题,所以需要弱引用倒计时按钮
        if(timeout<=0){
            //倒计时结束,关闭
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf.codeBtn setTitle:@"发送验证码" forState:0];
                [weakSelf.codeBtn setTitleColor:[UIColor whiteColor] forState:0];
                [weakSelf.codeBtn setUserInteractionEnabled:YES];
            });
        }else{
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf.codeBtn setTitle:[NSString stringWithFormat:@"%ds",timeout] forState:0];
                [weakSelf.codeBtn setTitleColor:[UIColor whiteColor] forState:0];
                [weakSelf.codeBtn setUserInteractionEnabled:NO];
            });
            timeout--;
        }
    });
    dispatch_resume(timer);
}

验证码倒计时.gif

下拉放大

#define headerHeight 200
@property (nonatomic, strong) UIImageView *iconIV;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.iconIV = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"scrollViewImage"]];
    self.iconIV.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, headerHeight);
    //放大时不改变宽高比例
    self.iconIV.contentMode = UIViewContentModeScaleAspectFill;
    self.iconIV.clipsToBounds = YES;
    /*
     头部视图 x y width 都是不能设置的 而图片要随着下拉 调整y轴 所以图片不能直接作为头部视图
     可以把图片放到一个view里 view作为头部 而且view不能剪切子视图
     */
    UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, headerHeight)];
    headerView.clipsToBounds = NO;//不剪切子视图超出部分!!!
    [headerView addSubview:self.iconIV];
    self.tableView.tableHeaderView = headerView;
    
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return 10;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    cell.textLabel.text = @"这是个下拉放大的Demo!";
    return cell;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    CGPoint offset = scrollView.contentOffset;
    NSLog(@"%f", offset.y);
    //根据打印值:向上移动 正数 向下移动 负数
    CGFloat height = headerHeight - offset.y;
    self.iconIV.frame = CGRectMake(0, offset.y, self.view.frame.size.width, height);
}
下拉放大.gif

你可能感兴趣的:(iOS开发必备干货)