iOS

UITextView上下左右留有边距的处理办法

UITextView的文字显示区域是由textContainer决定的,所以要解决这个问题需要从textContainer或相关的属性入手。

1.上下不留边距

UITextView有一个textContainerInset属性,默认为UIEdgeInsetsMake(8, 0, 8, 0),这就是UITextView顶部和底部留有间隙的原因,解决办法很简单,设置

textView.textContainerInset = UIEdgeInsetsZero;

2.左右不留边距

UITextView的左右边距是由textContainer属性的lineFragmentPadding控制的,其默认值为5.0,解决办法是设置

textView.textContainer.lineFragmentPadding = 0;

Link:

App判断系统是否开启代理(防抓包)

iOS 端:

- (void)checkHTTPEnable {
    NSDictionary * ref = (__bridge NSDictionary *)CFNetworkCopySystemProxySettings();
    BOOL enable = [[ref objectForKey:@"HTTPEnable"] boolValue];
    if (enable) {
        NSLog(@"开启了代理");
    }else {
        NSLog(@"没开代理");
    }
}

Android 端:

/*
 * 判断设备 是否使用代理上网
 */
private boolean isWifiProxy(Context context) {
    final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    String proxyAddress;
    int proxyPort;
    if (IS_ICS_OR_LATER) {
        proxyAddress = System.getProperty("http.proxyHost");
        String portStr = System.getProperty("http.proxyPort");
        proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
    } else {
        proxyAddress = android.net.Proxy.getHost(context);
        proxyPort = android.net.Proxy.getPort(context);
    }
    return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}

在网络数据请求之前,进行代理是否设置的判断。如果监听到手机系统打开了代理,我们就停止后面的数据请求操作,这样可以解决我们用户的数据不被其他不法手段获取。

Link:

去除iOS 项目中第三方库警告几种方式

去除全部第三方库的警告

在Podfile 文件前添加以下代码去除所有第三方库的警告

inhibit_all_warnings!

去除指定第三方库的警告

在指定Pod 后添加以下代码去除指定第三方库的警告

:inhibit_warnings => false

// 以下为参考代码
// pod 'LocalPod', :path => '../LocalPod', :inhibit_warnings => false

通过代码有条件去除第三方库的警告

pre_install do |installer|
  installer.analysis_result.specs_by_target.each_key do |target_definition|
    installer.analysis_result.specifications.each do |spec|
      source = spec.attributes_hash['source']
      source &&= source['git']
      next unless source && source.include?('cocoapods-repos')

      targets = (Array(target_definition) + target_definition.children)
      targets.each do |target|
        target.set_inhibit_warnings_for_pod(spec.root.name, true)
      end
    end
  end
end

// `source` 为 `Third.podspec` 中配置的 `s.source` ,可以根据自己的项目情况进行修改

Link:

使用子控制器分解复杂模块页面功能

我们在开发的过程中,通常会对复杂界面进行分模块处理,降低页面的复杂度,提高代码的可维护性。使用子控制器是一个很好的主意。

使用子控制器有以下好处:

1.无疑,对页面中的逻辑更加分明了。相应的View对应相应的ViewController。
2.当某个子View没有显示时,将不会被Load,减少了内存的使用。
3.当内存紧张时,没有Load的View将被首先释放,优化了程序的内存释放机制

使用方法:

  • addChildViewController:的同时调用addSubView:
[self addChildViewController:sfViewControllr];
[self.view addSubview:sfViewControllr.view];
  • 设置子视图的位置,并显示出来
sfViewControllr.view.frame = CGRectMake(0, 300, 1, 1);
[sfViewControllr didMoveToParentViewController:self];
  • 移除子视图
[sfViewControllr willMoveToParentViewController:nil];
[sfViewControllr removeFromParentViewController];
[sfViewControllr.view removeFromSuperview];

可能遇到的问题:
如果在子Controller中,把自己从父Controller中移除,在ios6中没问题,在iOS7中,会崩溃

[self willMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController]; //ios7中崩溃

暂时的解决方法,在子Controller中发通知,通知父Controller,移除子Controller

Link:

记录一次iOS 14 上非数字键盘第一相应者导致的崩溃问题

最近通过bugtags错误上报好几个iOS 14 上的崩溃问题,一直找不到崩溃的原因。崩溃信息如下:

Exception Codes: 0x00000000 at 0x0000000000000000
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[NSXPCConnection sendInvocation]: A NULL reply block was passed into a message meant to be sent over a connection. (generateAutocorrectionsWithKeyboardState:candidateRange:requestToken:completionHandler:)'

根据错误日志也无法分享出具体错误信息,于是找了台设备升级到iOS 14 bata2,发现这在iOS 14上是一个必现的问题,只要是非数字键盘,在键盘成为第一响应者时就会导致崩溃。排查了很久,发现在 debug 模式下不会产生,在 release 模式下是必现,于是想到了 runtime 对错误信息的hook,尝试注释掉相关的runtime,果然不会导致崩溃问题,然后采用折半查找法,排查到是以前对一个键盘问题的处理方法导致的:

Method methodSignatureForSelector = class_getInstanceMethod(self, @selector(methodSignatureForSelector:));
Method customMethodSignatureForSelector = class_getClassMethod(self, @selector(customMethodSignatureForSelector:));
method_exchangeImplementations(methodSignatureForSelector, customMethodSignatureForSelector);

该方法在针对键盘的特定事件进行过重写,导致在iOS 14 上键盘成为第一响应者时导致崩溃。

使用命令行解析bugly下iOS APP的崩溃日志

这几天 bugly 日志平台出现故障,无法解析iOS 用户上报的崩溃日志。无奈,只能使用最原始的命令行来解析日志信息。
首先我们需要准备好崩溃日志对应的 dSYM 文件。

  • 打开终端,执行命令 xcrun atos -o appName.app.dSYM/Contents/Resources/DWARF/appName -l 模块加载地址 第二个地址
  • 回车。稍等片刻就能看到错误具体信息流。

地址说明

Link:

  • [命令行工具atos解析iOS app的崩溃日志(bugly)]

Swift 获取文件MD5的方法

首先需要引入用的的Cocoa类CommonCrypto,由于CommonCrypto没有兼容 Swift,需要将以下导入到项目的 Bridging-Header.h 文件中:

#import <CommonCrypto/CommonCrypto.h>

由于移动设备的内存有限,以下代码实现是将文件分块读出并且计算md5值的方法,兼容Swift 3.0:

func fileMD5(_ path: String) -> String? {
    let handle = FileHandle(forReadingAtPath: path)
    if handle == nil {
        return nil
    }
    let ctx = UnsafeMutablePointer<CC_MD5_CTX>.allocate(capacity: MemoryLayout<CC_MD5_CTX>.size)
    CC_MD5_Init(ctx)
    var done = false
    while !done {
        let fileData = handle?.readData(ofLength: 256)
        fileData?.withUnsafeBytes {(bytes: UnsafePointer<CChar>)->Void in
            //Use `bytes` inside this closure
            //...
            CC_MD5_Update(ctx, bytes, CC_LONG(fileData!.count))
        }
        if fileData?.count == 0 {
            done = true
        }
    }
    //unsigned char digest[CC_MD5_DIGEST_LENGTH];
    let digestLen = Int(CC_MD5_DIGEST_LENGTH)
    let digest = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
    CC_MD5_Final(digest, ctx);
    
    var hash = ""
    for i in 0..<digestLen {
        hash +=  String(format: "%02x", (digest[i]))
    }
    
    digest.deinitialize()
    ctx.deinitialize()
    
    return hash;

}
Objective-C 下实现库:

Link:

How to detect the end of slider drag?

UISlider 是iOS 提供的一个系统滑动指示条,可以帮助我们快速实现需要进度展示的相关功能。我们可以通过添加滑动监听方法,来获取滑动变化。一般我们都会添加以下监听方法获取滑动监听:

[slider addTarget:self action:@selector(onSliderValueChanged:) forControlEvents:UIControlEventValueChanged];

但这种单参数的监听,只能获取到滑动值得改变,无法获取用户手势变化,这样给我们实现一些功能时带来不便。

我们可以通过添加多参数的监听,实现同时获取滑动值得变化和用户手势变化的通知:

[slider addTarget:self action:@selector(onSliderValueChanged:forEvent:) forControlEvents:UIControlEventValueChanged]
- (void)onSliderValueChanged:(UISlider*)slider forEvent:(UIEvent*)event {
    UITouch *touchEvent = [[event allTouches] anyObject];
    switch (touchEvent.phase) {     
        case UITouchPhaseBegan:
            // handle drag began
            break;
        case UITouchPhaseMoved:
            // handle drag moved
            break;
        case UITouchPhaseEnded:
            // handle drag ended
            break;
        default:
            break;
    }
}

Link: iPhone : How to detect the end of slider drag?

Done.

swift for 循环逆序写法

swift for 循环正序写法很简单:

for i in 0..< num {
    // do something
}

但如果我们简单的将for 循环的正序写法倒过来则会出现错误:

for i in num..>0 {
    // do somethink
}

以上写法会报下面的错误:

Can't form Range with upperBound < lowerBound

解决以上问题,我们可以这样写:

for i in (0..<num).reversed() {
    // do something
}

当然,如果你不满足上面的写法,也可以尝试下面的:

for i in stride(from: 3, through: 0, by: -1) {  
     print(i)  
}
Swift 的 stride 函数返回一个任意可变步长 类型值的序列。可变步长类型是可以设置偏移量的一维标量。
他有两个变种,
  • from,to,最后一个值将会严格小(大)于to的值
stride(from:3, to:0, by:-1) 表示3,2,1
  • from,through,最后一个值将会小(大)于等于through的值
stride(from:3, through:0, by:-1) 表示3,2,1,0

That's All.

Link: Can't form Range with upperBound < lowerBound

通过Xcode查看真机中应用程序的数据文件

有时候开发调试时,需要查看真机中应用的文件,比如sqlite之类的,可按以下步骤进行:

环境:Xcode Version 7.2.1,iPad2。

1)点击Xcode的Window菜单项,选择Devices选项。

2)点击左边设备一览中的iPad2,右边「Installed Apps」会显示出iPad上的所有第三方应用。

3)选中要查看的应用。

4)点击下面的设置按钮,选择「Download Container…」按钮,把应用数据下载到Mac上,生成一个.xcappdata文件。

5)在.xcappdata文件上点击右键,选择Show package contents 查看包内容,就可以看到真机应用程序的数据文件了。

Link: 【Xcode使用技巧】通过Xcode查看真机中应用程序的数据文件