iOS 开发 -- 项目拾疑_杂碎的知识点(1)

980 阅读11分钟
原文链接: www.jianshu.com

有段时间没写东西了,最近都是机械搭项目。趁着后台,出接口的这几天。
咱也定一个小目标,比如说-----
算了,不吹牛逼了,怕被打死。
最近刚做一个项目,准备在项目中穿插着写一点东西吧。

1杂碎的知识点
2讲一下项目大体的架构,分析一下项目用的三方的一些问题和选择
3讲一下,我这个菜鸡对项目网络层以及数据存储层的设计以及分析

项目没做完之前,我会不断更新杂碎的知识点这篇,等项目over.和你们好好唠唠项目整体。
好了不Xx了

1.1 UINavigationController

导航栏控制器基本是项目必备啊,所以我们UI大姐们也是想着花样搞这个东西。我们做这方面开发的都是到NVC(码字 有点烦,后面都这样简称了),基本构造做开发的都是知道,是一个容器类 下面的VCs都是放在 stack中的。所以说,在stack里面那么多VC都是公用一个navigationBar啊。UI大姐就喜欢一个VC 拥有一个不同的bar....欲哭无泪。
现在按按修改程度,一点一点写。

1.1.1bar 上的返回按钮太丑了 换掉0.0

~~第一种方法就是原本backItem不动,将文字移到看不见的地方,并且更换返回图片。个人感觉不是很好
~第二个方法就是整个替换掉leftItem,当然随之而来的问题就是要实现点击返回功能和左滑返回功能。
因为要实现左滑 最好还是重写一个NVC子类 而不是用appearce

//遵循手势的协议
@interface IGONavigationController ()

- (void)viewDidLoad{
    [super viewDidLoad];
    self.interactivePopGestureRecognizer.delegate = self;
}
//替换掉各个页面的返回按钮
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
    //设置返回按钮
    viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"fanhui"] style:UIBarButtonItemStyleDone target:self action:@selector(popViewControllerAnimated:)];
    [super pushViewController:viewController animated:animated];
}
//右滑返回的处理
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    BOOL ok = YES; // 默认为支持右滑反回
    if ([self.topViewController isKindOfClass:[RootViewController class]] ||
        [self.topViewController isKindOfClass:[Root1ViewController class]] ||
        [self.topViewController isKindOfClass:[Root2ViewController class]] ||
        [self.topViewController isKindOfClass:[Root3ViewController class]]) {
        return NO;
    }
    return ok;
}

要注意的是 开起左滑 返回 移动去去掉 NVC 的rootVC 不然就会出现空stack的问题,从而导致各种问题。

1.1.2bar 有的页面需要,有的页面不需要

这个也是挺烦的,我看到很多同鞋。在处理这个问题上,都会这么写。加入A页面需要,B页面不需要的话

A
- (void)viewWillAppear:(BOOL)animated {
    self.navigationController.navigationBar.hidden = NO;
}
B
- (void)viewWillAppear:(BOOL)animated {
    self.navigationController.navigationBar.hidden = YES;
}
- (void)viewWillDisappear:(BOOL)animated {
    self.navigationController.navigationBar.hidden = YES;
}

这种做法,也就效果勉强出来了,不过会带来一系列的问题
·····1 左滑 返回的时候NVC 无法分清那个VC 有Bar .出现显示问题
·····2当2个VC对应 statusBar 的字体颜色不一样的时候。上面这种做法,虽然隐藏了Bar 但在VC中重写preferredStatusBarStyle依旧没有作用的。NVC很难分清其中的界限。
正确的做法应该是
这种修改Bar 的工作还得交给代理去做,毕竟出现的意外情况,苹果的大牛们都考虑到了,代码要写在改写的地方。
因为我子类化得NVC用的地方很多,所以对于Bar的代理 我基本会放在rootVC中
下面的代码 就是在 SearchViewController和SearchResultController中隐藏了Bar
在ProductDetailViewController中对Bar的样式 进行了修改。

//在rootVC 中 遵循NVC代理 UINavigationControllerDelegate
//隐藏导航栏
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
    // 隐藏Bar
    BOOL isShowHomePage = [viewController isKindOfClass:[SearchViewController class]] || [viewController isKindOfClass:[SearchResultController class]];
    [self.navigationController setNavigationBarHidden:isShowHomePage animated:YES];
    UIImage *shadowImage = self.navigationController.navigationBar.shadowImage;
    UINavigationBar *bar = self.navigationController.navigationBar;

    if ([viewController isKindOfClass:[ProductDetailViewController class]]) {
        bar.shadowImage = [UIImage new];
        [bar lt_setBackgroundColor:[UIColor clearColor]];
    }else {
        bar.shadowImage = shadowImage;
        [bar setBackgroundImage:[UIImage imageNamed:@"导航栏"] forBarMetrics:UIBarMetricsDefault];
    }
}

这样 在左滑的时候消除了显示问题,以及在在隐藏了Bar的VC中无法控制statusBar的状态颜色问题。

1.1.3 每个bar完全不同

童鞋 全隐藏 自定义吧QAQ

1.2UINavigationBar

大家现在都是最低支持7以上了,太老的东西就不涉及了。
bar 有2个烦人的地方就是偏移量和透明度了

1.2.1偏移量

我们知道默认translucent = YES。就是Bar 是有透明度的。
在有透明度的情况下,系统默认automaticallyAdjustsScrollViewInsets属性是YES就是对于scrollerview的子类会默认content偏移64个单位。这样做的目的,既然Bar都是透明的了,系统就觉得你的scrollerView一定在会在(0,0)点,content偏移一个64个单位。在你滑动的时候,隐藏在Bar下面的Content会有一个模糊显示的效果。QAQ
如果你不想要这个偏移量
1 设置translucent = NO,既然Bar不透明。自然不需要偏移量喽
2 关闭这个偏移量,automaticallyAdjustsScrollViewInsets = NO
提一下像tableView,在storyboard 设置上下左右的约束。结果cell上面多了一块空白。就是这个问题。

1.2.2透明度

这种需求现在太多了,尤其都要做成淘宝那样的详情页,烦的一腿。现在我就在分析一下。
首先如果想滑动控制Bar的透明度,translucent 属性一定要设置成YES(默认)
当Bar 被设置成透明状态的时候


ED5A2936-7199-47B4-9E14-73E44F49BE74.png


还是这张图 _UIBackdropView 这个view是设置半透明毛玻璃效果的view(当然只在透明属性为YES 或者 没有背景图片的时候才会出现)
首先我们要去掉层特殊的视觉图层 利用设置BarBackground图片

- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;

千万不要用translucent = NO 这种方法来去掉视觉图层,都设置不透明了,那我们还玩按什么是吧。。。
这里注意的是我们设置的barTintColor 以及 background 都是对_UINavigationBarground这个Imageview来进行操作的。
这时候 我们就剩下Bar 和 BarBackground两层了
首先我们来说下Bar上面设置颜色的3个方法

//背景颜色 继承父类的 不过这个是设置bar 的背景颜色 
@property(nullable, nonatomic,copy)   UIColor   *backgroundColor UI_APPEARANCE_SELECTOR; // default is nil. Can be useful with the appearance proxy on custom UIView subclasses.
//这个是设置bar 上面的那层 barBackground imaeView的颜色
@property(nullable, nonatomic,strong) UIColor *barTintColor NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR;  // default is nil
//这个是item的颜色
@property(null_resettable, nonatomic,strong) UIColor *tintColor;

再来看下各个控件的高度 bar 本身是44 而barBackgoundImageView 确实64.。
这就是 为什么你设置了bar你颜色 或者 背景图片 。为什么status Bar 的背景也会跟着改变哦。
其实Bar 本身的颜色几乎很少设置,因为现在都要2个bar 一致才符合主流嘛

怎么改变barBackgroundImageView的背景颜色,又成了一个问题。因为刚才为了去掉视觉图层,我们给barBackgroundImageView 赋值了一个空图片。问题出来了,有了图片属性之后,给barBackgroundImageView设置背景颜色就没有用了,现在主流的做法是
查一个view进去 去调节这个 view的颜色 来控制bar 的背景颜色控制
写个类目 给 bar 加一个 - (void)iGo_setBackgroundColor:(UIColor *)backgroundColor

- (UIView *)backView
{
    return objc_getAssociatedObject(self, &backView);
}

- (void)backView:(UIView *)backView
{
    objc_setAssociatedObject(self, &backView, backView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)iGo_setBackgroundColor:(UIColor *)backgroundColor
{
    if (!self.backView) {
        [self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
        self.backView = [[UIView alloc] initWithFrame:CGRectMake(0, -20, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds) + 20)];    
        [self insertSubview:self.backView atIndex:0];
    }
    self.backView.backgroundColor = backgroundColor;
}

最后 我们需要调节透明度的页面
在滑动过程中 调节颜色的透明度 就OK拉

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    UIColor * color = [UIColor colorWithHexString:@"#fa2245"];
    CGFloat offsetY = scrollView.contentOffset.y;
    if (offsetY > 50) {
        CGFloat alpha = MIN(1, 1 - ((50 + 64 - offsetY) / 64));
        [self.navigationController.navigationBar lt_setBackgroundColor:[color colorWithAlphaComponent:alpha]];
    } else {
        [self.navigationController.navigationBar lt_setBackgroundColor:[color colorWithAlphaComponent:0]];
    }
}

2旋转处理

一半我我们的app不支持旋转的话 就会关掉下面这两个向选项



来禁止横屏。
但如果我们的app部分页面支持横屏,部分不支持的话就要做一些处理了
我们不需要对每一个VC都进行处理,只要对最外层的容器类VC重写一些方法。
例如我的所有VC都是由tabbar管理的
我会在tabbar的子类中

#pragma mark -- Orientation
//如果目前是竖屏 则不支持旋转,如果现在是横屏 则支持旋转
- (BOOL)shouldAutorotate{

    return !([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait);

}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationPortrait;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}

在某些需要旋转的VC继续重写这几个方法

//因为是个别页面是横屏 其他页面都是竖屏,这里可以直接返回YES 
//如果有特殊情况 也可以判断返回
#pragma mark -- orientation
- (BOOL)shouldAutorotate {
    return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeRight;
}

这样即使是横屏状态打开app 也会自动切换成竖屏。不过如果你在tabbar 在加一些东西的话可能会出现问题。
举个例子


2362FE5F-DDC4-48EA-951B-FBA57DB00A43.png


中间这个 取 的按钮 ,按照系统的是无法实现的。又不想重新自定义tabbar。这时候我就在tabbar中中间加上一个Button.来模拟tabbar item 的功能。
这时候 从横屏 进来之后,就会出现问题,因为bar 上是无法添加约束的,所以只能用frame 布局。 但是横竖屏的宽高大小 是相反的0.0
这时候就需要判断了

//可以用宽高大小判断 也可以用上面的设备方向判断
CGFloat width = kScreenHeight>kScreenWidth? kScreenWidth: kScreenHeight;
    UIButton *getGoodsButton = [[UIButton alloc] initWithFrame:CGRectMake(width/2 - kButtonWidth/2, 0, kButtonWidth, kButtonHeight)];

这只是一盒小例子,总之 处理屏幕旋转的时候要注意布局。尤其是没有约束控制的布局

3扫描

3.1淘宝的扫描效果(盗的图QAQ)


2BA14FEB-7E6F-499E-827A-9FC2E02E5B61.png

这个看起来很炫酷,其实就是一个网格的图片上下移动,把多余的部分Mask 掉就可以了。

//放个可视面积差不多大小的view,将imageview 加上去
UIView *tempView = [[UIView alloc] initWithFrame:VISIBLERECT];
//重点
    tempView.layer.masksToBounds = YES;
    [self.view addSubview:tempView];
    _scanningImageView = [[UIImageView alloc] init];
    [tempView addSubview:_scanningImageView];
    _scanningImageView.image = [UIImage imageNamed:@"扫描网格"];

上下的移动的代码就不贴,思路知道了。做起来很简单

3.2 unsupported type found

在设置AVCaptureMetadataOutputmetadataObjectTypes时候,一定要注意设置在载入session 之后不然就会出现 特别迷惑人的错误


E9F4D45F-0FA8-426D-AA5C-11A7FEB11253.png

我纳闷了好久 啊 。。。。。要记得 前后 顺序

[_session addInput:_input];
    [_session addOutput:_output];
    _output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
3.3 坐标转换

我们知道扫描的可视范围rectOfInterest坐标系统 和 我们 UIKit用的坐标系统不一样,我们在设置这个坐标的时候有两种做法
1,根据 两种坐标系 自己换算去 其实就是将屏幕左翻转 (x,y轴对调)
CGRectMake(y的起点/屏幕的高,x的起点/屏幕的宽,扫描的区域的高/屏幕的高,扫描的区域的宽/屏幕的宽)
2,通过系统的方法换算
在通过系统的方法换算的时候,出现了小问题

- (CGRect)metadataOutputRectOfInterestForRect:(CGRect)rectInLayerCoordinates NS_AVAILABLE_IOS(7_0);

在使用这个方法换算的时候始终是没有作用的,
其实方法是没错,但是调用的时机很重要
当我们收到AVCaptureInputPortFormatDescriptionDidChangeNotification通知的时候在进行左边转换就没有问题了
具体原因,我也没有找到。希望大神指点

[[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {
        _output.rectOfInterest = [_scanView metadataOutputRectOfInterestForRect:VISIBLERECT];
    }];

4UIButton

我们都知道button中有2个子控件 一个imageView 一个label。默认imageview 在左label 在右。我们控制好这几个控件 能满足好多需求
首先 button 是UIControl的子类
UIControl有控制content位置的属性

typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
    UIControlContentVerticalAlignmentCenter  = 0,
    UIControlContentVerticalAlignmentTop     = 1,
    UIControlContentVerticalAlignmentBottom  = 2,
    UIControlContentVerticalAlignmentFill    = 3,
};

typedef NS_ENUM(NSInteger, UIControlContentHorizontalAlignment) {
    UIControlContentHorizontalAlignmentCenter = 0,
    UIControlContentHorizontalAlignmentLeft   = 1,
    UIControlContentHorizontalAlignmentRight  = 2,
    UIControlContentHorizontalAlignmentFill   = 3,
};
@property(nonatomic) UIControlContentVerticalAlignment contentVerticalAlignment;     // how to position content vertically inside control. default is center
@property(nonatomic) UIControlContentHorizontalAlignment contentHorizontalAlignment; // how to position content hozontally inside control. default is center

只要我们合理的运用 content(imageview 和 label) 可以在button 内部的16个位置上。
但有时这个东西满足不了我们的需求
看个例子


FE0CDCAA-451F-4ACF-97BA-74694BA7548B.png


这种图片在右边 文字在左边,就令我们很蛋疼。看到这个需求有的童鞋可能就会自定义一个视图类了,其实不需要button的属性就可以帮助我们解决

@property(nonatomic)          UIEdgeInsets contentEdgeInsets UI_APPEARANCE_SELECTOR; // default is UIEdgeInsetsZero
@property(nonatomic)          UIEdgeInsets titleEdgeInsets;                // default is UIEdgeInsetsZero

可以设置图片文字的偏移量,不过此处有坑

#import "UIButton+Common.h"

@implementation UIButton (Common)

- (void)reverseImageAndTitle {
    CGRect imageFrame = self.imageView.frame;
    CGRect titleFrame = self.titleLabel.frame;
    self.titleEdgeInsets = UIEdgeInsetsMake(0, -imageFrame.size.width *2, 0,0 );
    self.imageEdgeInsets = UIEdgeInsetsMake(0,0, 0, -titleFrame.size.width*2);

}
@end

这边单位是px 不是pt 整个人都蒙了有木有。
不过还好发现了

5UITextField


C04C5E25-F123-4372-A707-B14ACD5D7EAD.png

设置左边图片 sb 上面没有对应的属性。。。庆幸的在textfield中还是有对应的属性 不过切图的时候记得要来拿左边的空白一起切下来

@property(nullable, nonatomic,strong) UIView    *leftView;        // e.g. magnifying glass
@property(nonatomic)  UITextFieldViewMode  leftViewMode;    // sets when the left view shows up. default is UITextFieldViewModeNever

6.tableView collectionView

1.圆角 始终是个争论的话题了。

各种花式的解决办法。
其实我认为,如果固定的页面 如果只有个别原角,无需优化
如果想tableview 这样大量重复的圆角,其实只要做设置一次缓存即可。开启光栅化,可解决性能问题。
什么 离屏渲染 CPU GPU线程切换消耗大量性能,我只做一次还不行meQAQ

//rasterize
    cell.layer.shouldRasterize = YES;
    cell.layer.rasterizationScale = [UIScreen mainScreen].scale;

不要各种贝塞尔 各种mask 简单点好。

2.对于selected

tableview 和 collectionview 都是支持单选的,只要重写 selected方法就行,系统在你创建cell类的时候也预留了这个方法。
不过要选择默认的selected 设置cell.seleted 是不行的

//collectionView
- (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition;

seleted 可以完美的实现默认选中 单选的效果。
对于对选 智能自定义了

3section 展开

只要用数组记录每个section 需要展示的cell 的数量 刷新view 就可以了

7VC设置背景图片

没有必要再放在一个imageview上面 直接在VC的view.layer上画就可以了

 self.view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"beijing"].CGImage)