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实现层级关系
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:垂直对齐好像现在还无法起作用,默认就是居中对齐的。
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