iOS | 技术点小计5

NSURLComponents

NSURLComponents *comps = [NSURLComponents new];
comps.queryItems = @[
  [NSURLQueryItem queryItemWithName:@"p1" value:@"v1"],
  [NSURLQueryItem queryItemWithName:@"p2" value:@"v2"]
];
comps.query; // p1=v1&p2=v2
[[NSURLComponents componentsWithString:@"https://www.baidu.com/path1/path2?p1=v1&p2=v2&"] queryItems]
//  {name = p1, value = v1},
//  {name = p2, value = v2},
//  {name = , value = (null)}
// scheme: https
// host: www.baidu.com
// path: /path1/path2
// query: p1=v1&p2=v2&

跳过文件iCloud备份

var values = URLResourceValues()
values.isExcludedFromBackup = true
try? fileUrl.setResourceValues(values)

NSFileManager 沙盒路径

let aURL = FileManager.default.url(for: .documentDirectory, in: .userDomainMask,
  appropriateFor: nil, create: false)

NSURL 获取资源属性

NSURL *aURL = [NSBundle.mainBundle URLForResource:@"test" withExtension:@"png"];
NSError *error;
NSDictionary *props = [aURL resourceValuesForKeys:@[
  NSURLFileSizeKey,
  NSURLFileResourceTypeKey,
] error:&error];
NSUInteger size = [props fileSize];

延迟崩溃(异常处理)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSSetUncaughtExceptionHandler(&exceptionHandler);
    return YES;
}

// 全局异常处理函数
void exceptionHandler(NSException * exception)
{
    NSLog(@"CRASH: %@", exception);//打印奔溃异常
    NSLog(@"Stack Trace: %@", [exception callStackSymbols]);//打印奔溃异常的函数调用堆栈信息
    NSLog(@"Oh, app, you will die!");
    //将信息保存本地或者上传到服务器(异步任务的时候要记得将任务同步起来,要不等不到异步任务完成程序就退出了,可以使用 dispatch_semophore来阻塞当前线程)。
    //也可以考虑获取当前 runloop 添加任务来保活线程,任务完成后程序退出
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    //模拟上传信息或写入沙盒操作
    [[AppDelegate new] requestTaskOrWriteToFile:^{
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_wait(semaphore, DISPATCH_TIME_FOREVER);//阻塞当前线程直到 semaphore 大于 0 时继续执行下面代码完之后退出
    NSLog(@"我可以走到这里了,之后程序就退出了");
}

// 模拟进行网络请求或写入沙盒操作
- (void)requestTaskOrWriteToFile:(void(^)(void))result
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"子线程 开始 处理耗时操作");
        sleep(5);
        NSLog(@"子线程 结束 处理耗时操作");
        result();
    });
}

iOS13显示自定义window

  • 在iOS13.0以上,必须设置window.windowScene,否则不显示
// 设置windowScene
if (@available(iOS 13.0, *)) {
        id scene = UIApplication.sharedApplication.connectedScenes.anyObject;
        if([scene isKindOfClass:UIWindowScene.class]) {
            self.windowScene = (UIWindowScene *)scene;
        }
    }
// 显示
[self setHidden:YES]

导出.a/.framework库的符号或.o

导出符号:

nm xxx.a > xxx_symbols.txt

如果上述命令执行失败,提示no symbols,则导出所有.o

$(ar -t xxx.a/xxx.framework/xxx) >> xxx_objects.txt

UITextView实现层级关系

UITextView

Cell+UITextView自动高度

_textView.scrollEnabled = NO;

图片裁剪区域绘制

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.view.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
    
    CGPoint point = [touches.anyObject locationInView:self.view];
    CGRect clipRect = CGRectMake(point.x, point.y, 200, 200);
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    UIBezierPath *drawPath = [UIBezierPath bezierPathWithRect:self.view.bounds];
    [drawPath appendPath:[UIBezierPath bezierPathWithRect:clipRect]];
    maskLayer.path = drawPath.CGPath;
    maskLayer.backgroundColor = UIColor.blackColor.CGColor;
    maskLayer.opacity = 0.7;
    maskLayer.fillRule = kCAFillRuleEvenOdd;
    
    [self.view.layer insertSublayer:maskLayer atIndex:0];
}

利用UITextView计算富文本高度

+ (CGFloat)boundingHeightForRichText: (NSAttributedString *)richText withFixingWidth: (CGFloat)fixWidth {
    return [self textViewSizeThatFits:CGSizeMake(fixWidth, CGFLOAT_MAX) withRichText:richText].height;
}

+ (CGFloat)boundingWidthForRichText: (NSAttributedString *)richText withFixingHeight: (CGFloat)fixHeight {
    return [self textViewSizeThatFits:CGSizeMake(CGFLOAT_MAX, fixHeight) withRichText:richText].width;
}

+ (CGSize)textViewSizeThatFits: (CGSize)fitSize withRichText: (NSAttributedString *)richText {
    UITextView *tv = [UITextView new];
    tv.contentInset = UIEdgeInsetsZero;
    tv.textContainerInset = UIEdgeInsetsZero;
    tv.textContainer.lineFragmentPadding = 0;
    tv.attributedText = richText;
    return [tv sizeThatFits:fitSize];
}

// 或者
+ (CGSize)textViewSizeToFit: (CGSize)fitSize withRichText: (NSAttributedString *)richText {
    UITextView *tv = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, fitSize.width, fitSize.height)];
    tv.contentInset = UIEdgeInsetsZero;
    tv.textContainerInset = UIEdgeInsetsZero;
    tv.textContainer.lineFragmentPadding = 0;
    tv.attributedText = richText;
    [tv sizeToFit];
    return tv.bounds.size;
}

ld: 64-bit LDR/STR not 8-byte aligned

加载本地图片

[template appendFormat:@"",NSBundle.mainBundle.resourcePath];

UITextView富文本解析

- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
    return NO;
}

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction {
    NSLog(@"shouldInteractWithURL: %@", URL);
    return NO;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSString *(^base64HTMLImgTagFor)(UIImage *image) = ^(UIImage *image){
        NSData *imageData = UIImagePNGRepresentation(image);
        return [NSString stringWithFormat:@"data:image/png;base64,%@", [imageData base64EncodedStringWithOptions: NSDataBase64EncodingEndLineWithCarriageReturn]];
    };
    
    NSString *(^replacedHtmlImgTag)(NSString *) = ^(NSString *imgTag){
        NSMutableString *imageName = [imgTag mutableCopy];
        [imageName replaceOccurrencesOfString:@"[bemoji]" withString:@"" options:0 range:NSMakeRange(0, imageName.length)];
        [imageName replaceOccurrencesOfString:@"[eemoji]" withString:@"" options:0 range:NSMakeRange(0, imageName.length)];
        UIImage *img = [UIImage imageNamed:imageName];
        return [NSString stringWithFormat:@"", imageName, base64HTMLImgTagFor(img), (NSInteger)img.size.width, (NSInteger)img.size.height];
    };
    
    NSString *templateText = @"点击链接:www.baidu.com [bemoji]apple[eemoji] [bc1]hello[/bc1] [bemoji]rose[eemoji]";
    
    NSMutableString *htmlText = templateText.mutableCopy;
    // handle urls
    [htmlText replaceOccurrencesOfString:@"" options:0 range:NSMakeRange(0, htmlText.length)];
    [htmlText replaceOccurrencesOfString:@"[/bc1]" withString:@"" options:0 range:NSMakeRange(0, htmlText.length)];
    
    NSError *error = nil;
    
    /// final handle emojis
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\[bemoji](([\\s\\S])*?)\\[eemoji]" options:0 error:&error];
    NSLog(@"NSRegularExpression: %@", error);
    
    NSArray *matches = [regex matchesInString:templateText options:NSMatchingWithTransparentBounds range:NSMakeRange(0, templateText.length)];
    [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSString *tag = [templateText substringWithRange:obj.range];
        [htmlText replaceCharactersInRange:[htmlText rangeOfString:tag] withString:replacedHtmlImgTag(tag)];
    }];
    
    _textView.linkTextAttributes = @{};
    NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithData:[htmlText dataUsingEncoding:NSUnicodeStringEncoding] options:@{
        NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType
    } documentAttributes:nil error:nil];
    _textView.attributedText = attr;
}

#if X, #ifdef X, #if defined X 区别

  • #if 后面跟条件表达式
  • #ifdef 后面只能接一个宏定义
  • #if defined的形式后面可以接多个宏定义

关于H5加载白屏问题检测

可以在webViewdidLoad之后,通过注入JS获取document.body下的子DOM节点是否会空,或者通过getElementById获取特定的DOM节点是否已经被渲染出来。

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {    
    // 如果是url特定页白屏,重新加载,否则不处理
    if(![webView.URL.absoluteString containsString:@"xxx"]) {
        return;
    }
    __weak WKWebView *weakWK = webView;
    [webView evaluateJavaScript:@"document.getElementById('SpecialView')" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        if([result isKindOfClass:NSNull.class]) {
            [weakWK reload];
        }
    }];
}

iOS 卡死崩溃分析

https://developer.apple.com/documentation/xcode/diagnosing-issues-using-crash-reports-and-device-logs
http://events.jianshu.io/p/e0375bd85746
https://www.jianshu.com/p/1f9f6814e3a2
https://blog.csdn.net/ByteDanceTech/article/details/114528899
https://jiuaidu.com/it/1878372/
https://www.cnblogs.com/sunyaxue/p/13019859.html

真机沙盒查看

Info.plist增加Application supports iTunes file sharing->YES

Apple开发工具下载

https://developer.apple.com/download/all/

macOS系统下载

https://support.apple.com/zh-cn/HT211683

macOS如何降级

https://zhuanlan.zhihu.com/p/580160875

WKWebView 设置Cookie

WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:@"document.cookie='X-Tag=gray;'" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[_webView.configuration.userContentController addUserScript:cookieScript];

NSURL *aURL = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:aURL];
[_webView loadRequest:request];

Reachability

https://developer.apple.com/library/archive/samplecode/Reachability/Reachability.zip

麦克风权限检测

// 检测麦克风授权
- (void)checkAudioAuthorization {
    kWeak(weakSelf);
    void(^showUngrantedAlert)(void) = ^{
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"麦克风未授权" message:@"请前往设置中开启,否则会导致通话没有声音。" preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"前往设置" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            NSURL *aURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
            if([UIApplication.sharedApplication canOpenURL:aURL]) {
                [UIApplication.sharedApplication openURL:aURL options:@{} completionHandler:nil];
            }
        }]];
        [weakSelf presentViewController:alert animated:YES completion:nil];
    };
    
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
    switch (status) {
        case AVAuthorizationStatusNotDetermined:
        {
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
                if(!granted) {
                    showUngrantedAlert();
                }
            }];
            break;
        }
        case AVAuthorizationStatusDenied:
        case AVAuthorizationStatusRestricted:
        {
            showUngrantedAlert();
            break;
        }
        default: break;
    }
    DDLogInfo(@" %s (%ld)", __func__, status);
}

OC 定义字符串枚举

typedef NSString *AVAudioSessionMode NS_STRING_ENUM;
OS_EXPORT AVAudioSessionMode const AVAudioSessionModeDefault;
OS_EXPORT AVAudioSessionMode const AVAudioSessionModeVoiceChat

Q: How do I fix "selector not recognized" runtime exceptions when trying to use category methods from a static library?

https://developer.apple.com/library/archive/qa/qa1490/_index.html#//apple_ref/doc/uid/DTS10004097

JS字符串中嵌入变量

console.log(`this is a test inner ${variable}`);

Jekyll

https://jekyllrb.com
http://jekyllthemes.org
sudo gem install -n /usr/local/bin jekyll
搭建博客
Themes

  • https://chirpy.cotes.page/
  • https://demo.themefisher.com/kross/
  • http://arkadianriver.github.io/spectral/

Swift hexString to UIColor

extension UIColor {
    static func hexColor(_ hexString: String, alpha: CGFloat = 1) -> UIColor? {
        var string = ""
        if hexString.lowercased().hasPrefix("0x") {
            string =  hexString.replacingOccurrences(of: "0x", with: "")
        } else if hexString.hasPrefix("#") {
            string = hexString.replacingOccurrences(of: "#", with: "")
        } else {
            string = hexString
        }

        if string.count == 3 {
            var str = ""
            string.forEach { str.append(String(repeating: String($0), count: 2)) }
            string = str
        }

        guard let hexValue = Int(string, radix: 16) else { return nil }

        let red = (hexValue >> 16) & 0xff
        let green = (hexValue >> 8) & 0xff
        let blue = hexValue & 0xff
        return .init(red: CGFloat(red)/255, green: CGFloat(green)/255, blue: CGFloat(blue)/255, alpha: 1)
    }
}

一种投机取巧快速实现自适应collection布局的方式

这种方式用到了私有API_rowAlignmentsOptionsDictionary,可能有不被过审的风险,仅供学习参考。

PS:垂直对齐好像现在还无法起作用,默认就是居中对齐的。

Left
Center
Right
class CollectionCell: UICollectionViewCell {
    @IBOutlet weak var textLb: UILabel!
}

extension String {
    func duplicated(times: Int, joined separator: String = "") -> Self {
        return (0.. UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as? CollectionCell else {
            return .init()
        }
        cell.textLb.text = items[indexPath.item]
        return cell
    }
    
    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }
}

iOS13 手动切换夜间模式

  • Toggle view controller's view's key window's UserInterfaceStyle
guard 
  let currentStyle = view.window?.overrideUserInterfaceStyle 
else {
  return
}
// Or you can toggle `overrideUserInterfaceStyle` 
// for the whole app's key window:
/*
  UIApplication.shared.delegate?.window?.overrideUserInterfaceStyle = xxx
*/
let newStyle: UIUserInterfaceStyle = currentStyle == .light ? .dark : .light
view.window?.overrideUserInterfaceStyle = newStyle

// save the theme style settings to user defaults
UserDefaults.standard.setValue(newStyle.rawValue, forKey: "AppThemeStyle")
UserDefaults.standard.synchronize()

// @available(iOS 13.0, *)
// open var overrideUserInterfaceStyle: UIUserInterfaceStyle

Apply the user last saved theme settings when the app is initiated:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow? {
        didSet{
            if let saved = UserDefaults.standard.value(forKey: "AppThemeStyle") as? Int,
               let style = UIUserInterfaceStyle(rawValue: saved) {
                window?.overrideUserInterfaceStyle = style
            }
        }
    }
}
  • Only toggle some specific one view controller's UserInterfaceStyle
let currentStyle = overrideUserInterfaceStyle
overrideUserInterfaceStyle =  currentStyle == .light ? .dark : .light

你可能感兴趣的:(iOS | 技术点小计5)