目录:
- 手动下载远程库来pod install / pod update
- app在后台时keyboard监听也会被触发
- 淘口令
- NSCopying实现
- 如何让alert显示在顶层
- 自带emoji的行间距问题
- app如何换色(清明黑白灰)
- UIApplicationDidBecomeActiveNotification
- viewDidLoad触发
1. 手动下载远程库来pod install / pod update
远程办公最怕的就是库版本发生变化,最近一拉最新代码经常就要pod install / pod repo update,每次都会卡死总要有各种问题,解决的方法大概就是github源就用4G,Google源就用代理+wifi,但是仍旧有的时候下不下来,于是我就在这里说一下如何直接从github下载然后让pod过去。
首先,如果是卡在了pod repo update,也就是从远端拿这些库最新的地址tag之类的,这个时候可以让能够过这一步的同事把/Users/用户名/.cocoapods/repos/trunk/Specs
整个文件夹压缩打包发给你,直接替换就好,这样就可以成功的过update那关啦。
如果木有同事装好,可以直接从git上下载最新的:
https://github.com/CocoaPods/Specs
Specs里面存了哪些东西呢?我们以一会儿想要实验的库libwebp为例吧:
其实里面就是各个版本有个json以及json tag文件,我们打开json看一下:
{
"name": "libwebp",
"version": "1.1.0",
"summary": "Library to encode and decode images in WebP format.",
"homepage": "https://developers.google.com/speed/webp/",
"authors": "Google Inc.",
"license": {
"type": "BSD",
"file": "COPYING"
},
"source": {
"git": "https://chromium.googlesource.com/webm/libwebp",
"tag": "v1.1.0"
},
"compiler_flags": "-D_THREAD_SAFE",
"requires_arc": false,
"platforms": {
"osx": "10.8",
"ios": "6.0",
"tvos": "9.0",
"watchos": "2.0"
},
"pod_target_xcconfig": {
"USER_HEADER_SEARCH_PATHS": "$(inherited) ${PODS_ROOT}/libwebp/ ${PODS_TARGET_SRCROOT}/"
},
"preserve_paths": "src",
"default_subspecs": [
"webp",
"demux",
"mux"
],
"prepare_command": "sed -i.bak 's/<inttypes.h>/<stdint.h>/g' './src/webp/types.h'",
"subspecs": [
{
"name": "webp",
"source_files": [
"src/webp/decode.h",
"src/webp/encode.h",
"src/webp/types.h",
"src/webp/mux_types.h",
"src/webp/format_constants.h",
"src/utils/*.{h,c}",
"src/dsp/*.{h,c}",
"src/dec/*.{h,c}",
"src/enc/*.{h,c}"
],
"public_header_files": [
"src/webp/decode.h",
"src/webp/encode.h",
"src/webp/types.h",
"src/webp/mux_types.h",
"src/webp/format_constants.h"
]
},
{
"name": "demux",
"dependencies": {
"libwebp/webp": [
]
},
"source_files": [
"src/demux/*.{h,c}",
"src/webp/demux.h"
],
"public_header_files": "src/webp/demux.h"
},
{
"name": "mux",
"dependencies": {
"libwebp/demux": [
]
},
"source_files": [
"src/mux/*.{h,c}",
"src/webp/mux.h"
],
"public_header_files": "src/webp/mux.h"
}
]
}
然后我们就可以去source的git下载相应tag的库啦
"source": {
"git": "https://chromium.googlesource.com/webm/libwebp",
"tag": "v1.1.0"
},
下载tar.gz或者zip都OK,然后解压放到我们project的本地pod里面:
注意哦你放进去的就是你从库仓库下载下来的文件,可能和你看到本地这个库的内容不一样,没关系的,你放进去再pod install过了以后就一样啦。
现在我们实现了下载需要的版本,并放入本地缓存,之后就可以来看如何pod install啦。在pod install的过程中,其实它就是比较了Podfile.lock
文件和本地缓存文件版本差异,如果有区别就去远端下载,所以现在我们需要让本地缓存的库和podfile.lock
里面一致,当然有一种方法是你把podfile.lock
里面的库版本降低到你本地的版本,这样你也不用从远端下载就可以成功pod install啦,只是这样的话你需要经常stash着这个变化。
那么本地缓存的版本由什么管理呢?其实就是项目/Pods/Manifest.lock
文件,你打开会发现和Podfile.lock
文件非常一致,只要把自己替换的库例如libwebp相关的内容都从Podfile.lock
拷到项目/Pods/Manifest.lock
即可,如果Podfile.lock
里面的版本不是最新的,你就手动改一下版本号以及spec checksum即可。
可参考:https://www.jianshu.com/p/06f507d2987d
例如podfile.lock里需要改成酱紫,manifest.lock必须相关的和podfile.lock保持一致,包括checksum的编号:
- libwebp (1.1.0):
- libwebp/demux (= 1.1.0)
- libwebp/mux (= 1.1.0)
- libwebp/webp (= 1.1.0)
- libwebp/demux (1.1.0):
- libwebp/webp
- libwebp/mux (1.1.0):
- libwebp/demux
- libwebp/webp (1.1.0)
SPEC CHECKSUMS:
……
KVOController: d72ace34afea42468329623b3379ab3cd1d286b6
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
那么CHECKSUMS的编号是如何生成的呢?我最开始以为是远程仓库的commit号,后来查了一下看到上面的refer文章才知道是这么生成的:
pod ipc spec /Users/用户名/.cocoapods/repos/trunk/Specs/1/9/2/libwebp/1.1.0/libwebp.podspec.json | openssl sha1
946cb3063cea9236285f7e9a8505d806d30e07f3
然后你再pod install就可以成功了吼~ (P.S. 想要知道自己卡在哪里,加上--verbose就可以啦)
2. app在后台时keyboard监听也会被触发
这个问题是我昨天晚上QA小姐姐发现的,就是如果我们注册了observer监听UIKeyboardDidHideNotification
以及UIKeyboardWillShowNotification
,那么即使我们的app在后台,当我们在其他app例如微信里面调用了键盘,自己app里面监听keyboard的方法仍旧会被触发,这样在两个app来回切换并且有微信键盘的时候就看着很奇怪。
于是我Google了一下 (https://stackoverflow.com/questions/34409566/keyboardwillshow-gets-called-for-other-apps-keyboards),解决方案就是在监听触发的handler函数里面最好先判断自己的app是不是active的,如果不是就return不去处理这个监听:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowNotificationHandler) name:UIKeyboardWillShowNotification object:nil];
- (void) keyboardWillShowNotificationHandler {
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
return;
}
……
}
3. 淘口令
各种复制口令到app然后会打开一个页面,最早感觉就是淘宝这么做的,它的是咋实现的呢?first step是从你的剪贴板拿到你复制的东西,second step我猜是把那串神奇的字符串发给后端,我们只要pop up一个webview,内容后端会来填充页面。
所以我们先来看一下从剪贴板拿你的magic string:
// 程序进入前台
-(void)applicationWillEnterForeground:(UIApplication*)application {
UIPasteboard* pasteboard= [UIPasteboard generalPasteboard];
NSLog(@"pasteboard: %@", pasteboard.string);
}
我拿拼多多的app来fake了一下,然后复制了一个口令打开拼多多看到的页面层级是这样的:(神奇的发现陌陌的反fake比拼多多做的好。。。
所以弹出的页面是原生的不是web哈~
但是如果你无网复制粘贴进拼多多就没有反应,所以虽然不是直接pop一个web让后端做,但应该是从剪贴板拿到string以后发给了后端,后端会告诉前端弹哪种view,并把需要的填充数据返还。
4. NSCopying实现
NSCopying的实现我们需要把属性一个一个的赋值就很烦,如果增加了新的属性还得手动记得改NSCopying,如果忘了就有可能犯错。
所以其实比较靠谱的是利用runtime,获取所有属性,然后赋值:
+ (nonnull id)copyObject:(nonnull NSObject *)object
{
Class clazz = [object class];
id objCopy = [[clazz alloc] init];
u_int count;
objc_property_t *properties = class_copyPropertyList(clazz, &count);
for (int i = 0; i < count ; i++) {
const char* propertyName = property_getName(properties[I]);
NSString *name = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
id value = [object valueForKey:name];
if([value respondsToSelector:@selector(copyWithZone:)]) {
[objCopy setValue:[value copy] forKey:name];
} else if (value != nil) {
[objCopy setValue:value forKey:name];
}
}
free(properties);
return objCopy;
}
然后如果有些属性比较特殊,你可以再单独处理一下~
5. 如何让alert显示在顶层
我们有个需求要在webview上面显示一个alert,但是如果拿topVC是nil,所以不能present alert。
如果我们想搞个alert在任何时候都能显示到最顶层可以这么搞,继承UIAlertController:
@interface TopLiveAlertController ()
@property (nonatomic, weak) UIWindow *previousKeyWindow;
@property (nonatomic, strong) UIWindow *alertContainerKeyWindow;
@end
@implementation TopLiveAlertController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)showInTop {
self.previousKeyWindow = [UIApplication sharedApplication].keyWindow;
[self.alertContainerKeyWindow removeFromSuperview];
self.alertContainerKeyWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.alertContainerKeyWindow.rootViewController = [UIViewController new];
self.alertContainerKeyWindow.windowLevel = UIWindowLevelAlert + 1;
[self.alertContainerKeyWindow makeKeyAndVisible];
[self.alertContainerKeyWindow.rootViewController presentViewController:self animated:YES completion:nil];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
[super dismissViewControllerAnimated:flag completion:completion];
[self.alertContainerKeyWindow removeFromSuperview];
self.alertContainerKeyWindow = nil;
[self.previousKeyWindow makeKeyAndVisible];
}
@end
- 这里牵扯到一个小问题,
windowLevel
是啥?
关于UIWindow可以参考一下这个https://www.jianshu.com/p/1c2ac0fa2e4a
其实windowLevel就是window的z轴,默认都是UIWindowLevelNormal (0)的:
UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar API_UNAVAILABLE(tvos);
6. 自带emoji的行间距问题
参考:https://www.jianshu.com/p/715181fbc28b
这个又是一个很烦人的问题,就是我们正常的文字如果用YYTextLayout *textLayout = [YYTextLayout layoutWithContainer:container text:attributeString];
获取高度完全木有问题,例如一个14font的文字可能计算完的高度是16,但是如果这行包括了emoji,这行的高度可能算出来就是24了。
于是如果我们的聊天气泡的高度是根据计算出啦的行高+上下间距来定的高度,就会出现在有emoji的时候行高突然变多。这个问题我实在很头疼,所以换了个方法来做,我们尝试固定行高,然后不加边距。这样其实无论是文字还是文字&emoji都要放进一样的高的container~
类似酱紫:
YYTextLinePositionSimpleModifier *modifier = [YYTextLinePositionSimpleModifier new];
modifier.fixedLineHeight = 24;
YYTextContainer *container = [YYTextContainer new];
container.size = CGSizeMake(constrainedWidth, 10000);
container.linePositionModifier = modifier;
YYTextLayout *textLayout = [YYTextLayout layoutWithContainer:container text:attributeString];
return CGSizeMake(textLayout.textBoundingSize.width, textLayout.textBoundingSize.height);
但加入了emoji以后的基线偏移我就实在解决不了了头秃。。
7. app如何换色(清明黑白灰)
这个就和我之前好像写过一个主题换色是一个道理,就也是把颜色都写到配置文件,然后用啥就用它的key。但这个注意UI千万得规定一个用色标准、按小哥哥的说法就是色库,要不就很麻烦每次得自己加很多颜色的key value。。。
8. UIApplicationDidBecomeActiveNotification
这个通知大家都很熟悉了,当app的状态变为active就会收到,一个很熟悉的场景就是我们退到后台以及回到前台。
但是有一个比较特殊,在iOS13的时候如果我们app已经在前台,用户下拉通知栏可以看到其他app的push,这个时候会先resign active,在become active,即使用户还没回到我们app,还在专心的读push。
但是如果你下拉的是右上角的工具栏(有手电筒、亮度等),则只会resign active,当你上滑把工具栏收起再次看到app的时候才会become active。
App的各种状态可以参考:https://www.jianshu.com/p/ee4add97c96a,经常会搞不清background和resign active,其实就是从active到inactive会发resign active通知,如果从inactive到background则是enter background通知。
9. viewDidLoad触发
我以前一直以为viewDidLoad是只有页面最开始出现的时候才会触发,但是这周遇到一个问题是当VC都快销毁的时机还出发了viewDidLoad,然后看堆栈是由于VC的getView方法被调用了。
但不是每次你调用vc.view就会执行viewDidLoad哦,听起来也很奇怪,应该是懒加载的,因为上个场景可能view已经被销毁了,或者VC刚初始化你就调用了他的view,所以当VC的view第一次nil被读取的时候就会触发viewDidLoad吼。