那些年遇到过的坑之 iOS

如果你有踩过一些坑,想和大家分享,欢迎联系我,我会整理后放到这里。也可以描述自己跳坑的经过以及最后如何飞升而出
email:569021230@163.com

启动

LaunchScreen.storyboard(启动界面缓存不更新问题)

项目开发中遇到需要更换启动界面 logo 的需求,启动界面实现是使用默认地 LaunchScreen.storyboard + UIWindow 实现。之所以使用 window 是因为设计有个需求 “启动时间不能低于一秒,否则用户看不到启动界面内容”。嗯~~,我只是笑笑,不锤人(俺,直接就是一刀)
这里写图片描述

其实更通用的场景是使用 UIWindow 实现开屏广告,这里就不详述了。

本来很简单的事情,换完 logo 才发现,我擦!暗藏玄机。启动 logo 还是旧的,window 里面是最新的 logo。我当时整个是黑人脸问好。

还好问题不难解决,通过一番搜索方案如下

  1. 删除 App -》重启手机 -》重新安装 App。
  2. 不使用 Assets.xcassets 把启动 logo 放到工程路径里,然后重命名 logo。最后使用新的 logo,问题解决。

第一个方案,你自己想办法怎么和用户解释。这里有一个现成的你可以参考 Due to the cached stuff in Springboard - you don’t see our new awesome launch screen image! Please reboot your device and open our app again!。看看老板会不会打死你。

第二个方案在实践的时候如果还是不行可以尝试以下方法解决:

  1. LaunchScreen.storyboard 里把 UIImageView 引用的图片名字去掉 png 拓展名。
  2. 放弃 LaunchScreen.storyboard,使用全屏的启动图片
  3. 烧几柱香静一静

另外,有文章提到说可以修改 Assets.xcassets 里面图片的名字实现。在我实践的时候发现,这会导致启动时 LaunchScreen.storyboard 找不到对应图片。可能是我用的姿势不对?

多媒体

AVAsset

AVAsset 允许使用 loadValuesAsynchronously 方法以异步的方式加载一些多媒体属性。之所以有这个方法,是因为 AVFoundation 框架默认只有你在访问一些属性的时候才会去加载数据,对一些网络多媒体来说,这样会造成线程的阻塞。所以需要这个方法来预加载。

需要注意的是,如果你使用的网络资源是 m3u8 格式,此时请求 playable 属性值永远不会反悔 fail 即使没有网络。对于这种格式如果之有 playable 时,框架不会做任何网络请求来确定是否后 HLS 文件可供使用。这个时候你可以同时添加一些别的属性,比如 tracks

ViewController

UICollectionView

scrollToItemAtIndexPath:atScrollPosition:animated:

本人在实际项目中出现了该方法无效的问题。原因就在于,这个方法生效必须在页面相关参数初始化完成后才能生效,最稳妥的方法是在 layoutSubviews() 第一次调用之后再使用这个方法。

同样的,过早的调用 scrollRectToVisible(_:animated:) 也是没有任何效果的

UINavigationController

实现方法有几种:

  • UINavigationBar.appearance() 通过设置 setBackgroundImage(image, for: .default) 实现
  • self.navigationController?.navigationBar.setBackgroundImage(image, for: .default)。如果需要透明度,还需要设置 elf.navigationController?.navigationBar.isTranslucent = true

上面两种设置的有点是简单、代码量小。

需要注意的是,图片不能太小,如果图片高度不够,会导致 导航条 的背景色和 状态栏 的背景色不一样。高度也好确定就是:导航栏高度 + 刘海高度 + 状态栏高度


但是如果你要想实现更多动态的定制化内容,还有一种基于 UINavigationBar 视图层级实现的效果,可以保证只要代码写的六,任何效果都可以。

实现方式就是,首先设置 isTranslucent = true,然后 navigationBar.insertSubview(customView, at: 0)

然后就是你的 showtime。

手势

手势中 cancelsTouchesInView 和 delaysTouchesBegan 的区别
界面层级如图所示




测试代码如下:

@interface ViewController ()

@property (strong, nonatomic) IBOutlet UIView *blackView;

@property (strong, nonatomic) IBOutlet UIView *originView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    UITapGestureRecognizer *gestureBlack = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapBlack:)];
    gestureBlack.cancelsTouchesInView = YES;
    [self.blackView addGestureRecognizer:gestureBlack];

    UITapGestureRecognizer *gestureOrigin = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOrigin:)];
    gestureOrigin.delaysTouchesBegan = YES;
    [self.originView addGestureRecognizer:gestureOrigin];
}

- (IBAction)btn2Clicked:(UIButton *)sender {
    NSLog(@"btn 02 clicked");
}
- (IBAction)btn1Clicked:(UIButton *)sender {
    NSLog(@"btn 01 clicked");
}

- (void)tapBlack:(UITapGestureRecognizer *)gesture {
    NSLog(@"tap black");
}

- (void)tapOrigin:(UITapGestureRecognizer *)gesture {
    NSLog(@"tap origin");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touch begin");
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touch cancelled");
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touch ended");
}

@end


日志如下:

2016-01-07 15:20:42.810 UserInteractionTest[6641:3264236] touch begin
2016-01-07 15:20:42.908 UserInteractionTest[6641:3264236] touch ended
2016-01-07 15:21:01.026 UserInteractionTest[6641:3264236] touch begin
2016-01-07 15:21:01.091 UserInteractionTest[6641:3264236] tap black
2016-01-07 15:21:01.092 UserInteractionTest[6641:3264236] touch cancelled
2016-01-07 15:21:06.542 UserInteractionTest[6641:3264236] touch begin
2016-01-07 15:21:06.607 UserInteractionTest[6641:3264236] tap black
2016-01-07 15:21:06.607 UserInteractionTest[6641:3264236] touch cancelled
2016-01-07 15:21:14.041 UserInteractionTest[6641:3264236] tap origin
2016-01-07 15:21:20.341 UserInteractionTest[6641:3264236] btn 02 clicked

从日志可以发现,当设置 gestureBlack.cancelsTouchesInView = YES 时,会触发 begin 和手势对应的 selector 同时 cancel 此次事件。当点击 btn01 时,btn01 的事件不会触发,但会触发 blackView 的手势事件。
当设置 gestureOrigin.delaysTouchesBegan = YES 时,所有的 touches 相关的方法不会触发,当点击 btn02 时会触发 btn2 的事件,不会触发 originView 的 手势事件。

这里需要注意,如果 blackView 和 originView 使用自定义 view 并且重写了其 touches 相关的方法,会有两种情况
1. 调用了对应的 super 方法
此时,调用顺序如下

2016-01-07 15:44:15.867 UserInteractionTest[6658:3270227] tap origin
2016-01-07 15:44:18.118 UserInteractionTest[6658:3270227] customView touch begin
2016-01-07 15:44:18.119 UserInteractionTest[6658:3270227] touch begin
2016-01-07 15:44:18.199 UserInteractionTest[6658:3270227] tap black
2016-01-07 15:44:18.201 UserInteractionTest[6658:3270227] customView touch cancelled
2016-01-07 15:44:18.201 UserInteractionTest[6658:3270227] touch cancelled
  1. 没有调用 super 方法
    此时只会调用自定义 view 中相关 touches 方法。

特别注意:
只有当 begin moved cancelled ended 全部被重写并且都没有调用 super 方法时 touches 才不会传到父视图,否则除了会调用自定时 view 的 touches 方法,也会调用父视图的 touches 方法。这样做是为了防止点击事件的错乱

动画

需求:
一个弹性效果,如果点中正在弹的控件则弹跳动画停止,开始缩放动画,并且点中时通知其他控件

问题:
在弹跳动画快结束的时候如果控件被点中,会出现其他控件收到了点中通知,但是没有出现缩放动画。

实现:
实现动画的代理方法

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

根据 flag 判断如果是被动结束(如被 cacel),则开始缩放动画。

坑:
在使用动画时 iOS 会生成一个 presentationLayer 来执行动画,控件本身会被暂时隐藏,动画过程中- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;犯法会首先执行,然后控件的 presentationLayer 才会被销毁,这样就会导致在动画快结束时点击出现动画执行完成却还能被点中的问题

填坑:
(不完美方法)
判断是否点中的时候,首先判断控件是否还有对应的动画

if (![control animationForKey:@"key"]) {
    // click is invalid
}


使用上面的方法需要把 removedOnCompletion 属性设为 YES。或者在 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag; 方法中将动画移除

文字控件

UILabel height 和 font 的 size 相同时,底部总是会被截断一部分

这个问题和字体的设计有关,在字体中,上面总是有一部分空白。如下图
此图来自当时所在公司的设计美眉

粉线和字母“d”的上部有一部分空白,并且这部分空白的大小和字体大小正相关。这个问题的根源目前不清楚,但是可以通过计算获取正确的字体大小。关于这个问题,可以参考 How to set font size to fill UILabel height?
这段代码是核心

let testStringHeight = labelText.sizeWithAttributes(
                    [NSFontAttributeName: font.fontWithSize(fontSizeAverage)]
                ).height

这个问题还可以通过 AutoLayout 方式解决。UILabel、UIImageView 等控件有自动调整能力。当不指定控件的高度时,其高度会在计算 AutoLayout 时自动计算。原理和上面相似,但是会减少一定的代码量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值