iOS零散知识收集

收集一些平时在iOS开发中碰到的问题解决方法

WKWebView中的Javascript交互

WKWebView中,Javascript与OC的交互变的十分简单。只需要通过WKScriptMessageHandler代理中的userContentController:didReceiveScriptMessage:即可在网页中让Javascript发送消息给OC。

首先试着写一段js脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var c=document.createElement('input');
c.type='checkbox'; 2.name='accept'; c2.id='accept'; c.checked=true;
var label = document.createElement('label');
label.htmlFor = 'accept';
label.appendChild(document.createTextNode('accept'));
document.getElementsByTagName('body')[0].appendChild(c);
document.getElementsByTagName('body')[0].appendChild(label);

var script = document.createElement('script');
script.innerHTML = \"
document.getElementById('accept').onclick = function toggle() {
var obj = document.getElementById('accept');
window.webkit.messageHandlers.accept.postMessage({'accept':obj.checked});
};
\";
document.getElementsByTagName('body')[0].appendChild(script);

这段js很简单,就是添加了一个checkbox元素,然后在点击事件中通过postMessage方法就可以在OC获取到js中传过来的对象。

接下来则是配置WKUSErContentController:

1
2
3
4
5
6
7
8
9
10
11
12
13
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = [WKUserContentController new];
//在文档末尾追加javascript
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[configuration.userContentController addUserScript:script];
//添加需要用作连接的对象
[configuration.userContentController addScriptMessageHandler:self name:@"accept"];

WKPreferences *preferences = [[WKPreferences alloc] init];
preferences.javaScriptEnabled = YES;
configuration.preferences = preferences;

self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

在这里通过addScriptMEssageHandler方法定义了一个js通知oc的对象accept,就是js中的messageHandlers后边声明的对象。

最后ViewController实现WKScriptMessageHandler代理,通过下列方法即可获得传输过来的数据:

1
2
3
4
5
# pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"message name: %@, body: %@", message.name, message.body);
//...
}

这里通过message.name可以获得传输对象的名称,就是上边定义的accept,而message.body则是获取到从js中传过来的数据


iOS应用内切换语言

How to force NSLocalizedString to use a specific language
ios开发应用内实现多语言自由切换

使用系统内置的NSLocalizedString(key,comment)函数即可获得对应的本地化语句。如果需要在应用内切换其他语言,可以自行添加一个定义

1
2
#define CustomLocalizedString(key, comment) \
[[NSBundle bundleWithPath: [[NSBundle mainBundle] pathForResource: [NSString stringWithFormat:@"%@",[[[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"] firstObject]] ofType:@"lproj"]] localizedStringForKey:(key) value:@"" table:nil]

然后切换语言需要修改AppleLanguages中的数组数据位置

1
2
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"en-US", @"zh-Hant", @"zh-Hans", nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize];

将需要切换的语言放置到第一个元素中即可


页面组件自适应导航栏

Status bar and navigation bar appear over my view’s bounds in iOS 7

当使用Interface Builder来构建页面的时候,如果是UIScrollVIew或者其子类UITableView的时候,当页面显示了NavigationBar的时候,UIScrollVIew部件会自动将坐标下移到NavigationBar下方,
这是因为automaticallyAdjustsScrollViewInsets属性为YES,如果不希望scroll view自动适应,将其设置为NO即可。

如果是普通的页面,一般都会直接拖到最顶层,如果这个时候页面显示了NavigationBar时,会将页面布局的一部分遮挡掉,以往的做法是将布局下移64,但是iOS7之后可以利用edgesForExtendedLayout来设置:

1
2
3
4
5
6
- (void)viewDidLoad {
if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) {
self.edgesForExtendedLayout = UIRectEdgeLeft | UIRectEdgeRight | UIRectEdgeBottom;
}
...
}


跳转页面之后背景半透明

用presentViewController一个背景颜色半透明的模态视图

当使用presentViewController方法弹出下一个UIViewController的时候,如果需要这个视图背景半透明,需要设置如下:

1
2
3
4
5
6
UIViewController *viewControllers = [UIViewController new];
self.definesPresentationContext = YES;
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
viewController.backgroudColor = [UIColor colorWithWhite: 0.1 alpha: 0.5];
//如果源视图不是NavigationController子视图,直接用self即可
[self.navigationController presentViewController:viewController animated:NO completion:nil];


隐藏状态栏

IOS7如何隐藏状态栏,貌似之前的没效果了
How do I hide the status bar in a Swift iOS app?

在需要隐藏状态栏的ViewController中重写prefersStatusBarHidden方法

1
2
3
- (BOOL)prefersStatusBarHidden {
return YES;
}

然后在需要更改隐藏状态的地方调用setNeedsStatusBarAppearanceUpdate方法

1
[self setNeedsStatusBarAppearanceUpdate]

swift写法

1
2
3
override func prefersStatusBarHidden() -> Bool {
return true
}


在应用中打开另一个应用

利用openURL,在IOS应用中打开另外一个应用
canOpenUrl - This app is not allowed to query for scheme instragram iOS9
iOS: Access app-info.plist variables in code

首先需要在info.plist注册自定义的URL scheme

1
2
3
4
5
6
7
8
9
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>test1</string>
</array>
</dict>
</array>

iOS9以后,还需要添加以下属性:

1
2
3
4
<key>LSApplicationQueriesSchemes</key>
<array>
<string>test2</string>
</array>

这里意为应用可以打开schemetest2的应用。
然后在代码中可以使用openUrl函数打开应用:

1
2
3
4
5
6
7
8
9
//获取scheme
let prefix = (Bundle.main.object(forInfoDictionaryKey: "LSApplicationQueriesSchemes") as! NSArray)[0]
let url = URL(string: "\(prefix):client_id=test1&scoe=wopi&platform=iOS&app=test1&action=12345")
if UIApplication.shared.canOpenURL(url! as URL) { //判断能否打开
//打开应用
UIApplication.shared.openURL(url! as URL)
} else {
print("can not go to this app!")
}

接下来应用可以在AppDelegate类中的handleOpenUrl方法中获取到传过来的参数:

1
2
3
4
5
6
7
8
9
10
11
func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
print(url)

let viewController = self.window?.rootViewController
let message = url.absoluteString
let alertController = UIAlertController.init(title: "url", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction.init(title: "OK", style: .default, handler: nil))
viewController?.present(alertController, animated: true, completion: nil)

return true;
}


在网页中打开应用

在网页<script>块中直接定义跳转的scheme为应用自定义的scheme即可

1
2
3
4
5
//如果无法打开应用则跳转到AppStore中
setTimeout(function () {
window.location = "https://itunes.apple.com/cn/app/microsoft-word/id586447913"
}, 25);
window.location = "test1://";

UIButton 内容左对齐

How to set the title of UIButton as left alignment?

设置contentHorizontalAlignment属性

1
[button setContentHorizontalAlignment: UIControlContentHorizontalAlignmentLeft];

或者使用UIEdgeInsetsMake(top, left, bottom, right)方法来设置缩进,正数为缩进,负数为突出

1
[button setContentEdgeInsets: UIEdgeInsetsMake(0, -20, 0, 0)];


裁剪照片

How to crop an image from AVCapture to a rect seen on the display

通过AVCaptureSession以及AVCaptureStillImageOuput获取的照片默认填充整个屏幕,与自定义的显示屏幕并不相同,因此需要裁剪照片至所见区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (UIImage *)cropImage: (UIImage *)image {
//获取需要裁剪的矩形
CGRect outputRect = [self.captureVideoPreviewLayer metadataOutputRectOfInterestForRect:self.captureVideoPreviewLayer.bounds];
CGImageRef takenCGImage = image.CGImage;
size_t width = CGImageGetWidth(takenCGImage);
size_t height = CGImageGetHeight(takenCGImage);
CGRect cropRect = CGRectMake(outputRect.origin.x * width, outputRect.origin.y * height, outputRect.size.width * width, outputRect.size.height * height);

CGImageRef cropCGImage = CGImageCreateWithImageInRect(takenCGImage, cropRect);
//需要使用原图的朝向
UIImage *croppedImage = [UIImage imageWithCGImage:cropCGImage scale:image.scale orientation:image.imageOrientation];
CGImageRelease(cropCGImage);
return croppedImage;
}

从照片文件夹中获取图像资源写入到临时文件夹

ALAsset , send a photo to a web service including its exif data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ALAsset *selectedAsset = [self.selectAssets objectForKey:key];
if (selectedAsset) {
long long byteSize = selectedAsset.defaultRepresentation.size;
NSMutableData *rawData = [[NSMutableData alloc] initWithCapacity:byteSize];
void *bufferPointer = [rawData mutableBytes];
NSError *error = nil;
[selectedAsset.defaultRepresentation getBytes:bufferPointer fromOffset:0 length:byteSize error:&error];
if (error) {
NSLog(@"get asset data error: %@",error);
}
rawData = [NSMutableData dataWithBytes:bufferPointer length:byteSize];
[cloudObject setFilesize:[NSString stringWithFormat:@"%lld", byteSize]];

NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
if ([rawData writeToFile:filePath atomically:YES]) {
[cloudObject setTempFilePath:filePath];
NSLog(@"current file path: %@", filePath);
}
}

通过PHAsset获取资源文件大小

How can I determine file size on disk of a video PHAsset in iOS8

iOS8之后,ALAsset被标记为不推荐,取而代之的是PHAsset。如果要获取文件大小,不能使用asset.defaultRepresentation.size了,需要用到以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
PHAsset *asset = [[PHAsset fetchAssetsWithLocalIdentifiers:@[cloudObject.tempFilePath] options:nil] firstObject];
if (asset.mediaType == PHAssetMediaTypeImage) {
PHImageRequestOptions *imageOptions = [[PHImageRequestOptions alloc] init];
imageOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
imageOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
imageOptions.synchronous = YES;
imageOptions.networkAccessAllowed = NO;
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:imageOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
NSLog(@"length %f",imageData.length/(1024.0*1024.0));
}];
} else if (asset.mediaType == PHAssetMediaTypeVideo) {
PHVideoRequestOptions *videoOptions = [[PHVideoRequestOptions alloc] init];
videoOptions.version = PHVideoRequestOptionsVersionOriginal;
videoOptions.networkAccessAllowed = NO;
[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:videoOptions resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {

if ([asset isKindOfClass:[AVURLAsset class]]) {
AVURLAsset *urlAsset = (AVURLAsset *)asset;
NSNumber *size;
[urlAsset.URL getResourceValue:&size forKey:NSURLFileSizeKey error:nil];
NSLog(@"size is %f",[size floatValue]/(1024.0*1024.0));
NSData *data = [NSData dataWithContentsOfURL:urlAsset.URL];
NSLog(@"length %f",[data length]/(1024.0*1024.0));
}
}];
}

不要忘了引入框架@import Photos


CMTimeMake

CMTimeMake和CMTimeMakeWithSeconds 详解

CMTimeMake(a,b) : a当前第几帧, b每秒钟多少帧.当前播放时间a/b
CMTimeMakeWithSeconds(a,b) : a当前时间,b每秒钟多少帧

更新播放时间

使用addPeriodicTimeObserverForInterval:queue:usingBlock方法可以监听到播放时间的变化

1
2
3
4
5
6
7
8
9
__weak typeof(self) weakSelf = self;
id playerObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1.0, NSEC_PER_SEC) queue:NULL usingBlock:^(CMTime time) {
Float64 interval = CMTimeGetSeconds(time);
NSInteger seconds = (NSInteger)interval % 60;
NSInteger minutes = ((NSInteger)interval / 60) % 60;
NSInteger hours = (NSInteger)interval / 3600;
NSString *currentTime = [NSString stringWithFormat:@"%02ld:%02ld:%02ld", (long)hours, (long)minutes, (long)seconds];
weakSelf.timeView.text = currentTime;
}];

使用完需要移除观察者[self.player removeTimeObserver: playerObserver]