启动页

iOS13以后,苹果已经不允许使用launch image方式设置启动页,仅允许launch screen方式,几点注意事项:

  1. 只需要出2x、3x图,不再需要折腾一堆图片;
  2. 为了保证在所有机型上不出现图片的压缩拉伸,出图的时候需要注意, 图片的上下区域应该和视图背景色一致,尽量全白
  3. 为了让启动页多待一会,需要保持AppDelegate的didFinishLaunchingWithOptions函数不结束,如显示启动页的过程完成自动登录操作。

图片性能优化

  1. 使用Image Asset Catalogs管理图片
  2. init?(named: String) 常用于加载小且常用的 icon,其初始化的图片,占用的缓存只会在程序退出时才清空,即使消除强引用仍会占用缓存。而 init?(contentsOfFile: String) 初始化的图片,在没有强引用时便会自动销毁。

而 init?(contentsOfFile: String) 使用有一些注意点。该方法的参数是图片的全路径,所以需要通过 Bundle 来获取,而且需要带上后缀名。需要注意的是,如果图片放置在 Assets.xcassets 中,Bundle 是无法获取到的,需要直接复制到项目中。

KVC

KVC的应用场景:
1.字典转模型 ,简化代码量

2.修改系统的只读变量: 例如自定义tabBar的时候,由于tabBar是只读属性,只能用KVC赋值.

3.可以任意修改一个对象的属性和变量(包括私有变量)

4.可以通过运算符层次查找对象的属性:keyPath

1
2
3
4
Teacher *t = [Teacher alloc ] init];
t.chiild = [Child alloc ] init];
t.child.book = [Book alloc] init];
XBLog(@"%@",ValueForKeyPath(@"child.book"));

KVO

KVO(Key-Value Observing)监听属性值,可以用来动态更新页面

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
27
28
29
30
31
32
33
34
35
36
37
import UIKit

//属性前加上@objc dynamic实现动态派发
class Preson: NSObject {
@objc dynamic var age = 0
}

class ViewController: UIViewController {

let p = Preson()

override func viewDidLoad() {
super.viewDidLoad()
//为p对象的age属性设置监听。
//options为枚举数组,传入了new和old两个值,表示我们需要监听改变前和改变后的值。
//context是Any类型,可以传入任意类型数据,方便在监听到变化时获取该数据。
p.addObserver(self, forKeyPath: "age", options: [.new, .old], context: nil)
}

//监听
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "age" {
print("age被改变了")
print(change as Any)
}
}

//释放监听
deinit {
p.removeObserver(self, forKeyPath: "age")
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
p.age = 20
}

}

iOS 不常用功能

  1. UIVisualEffectView、UIBlurEffect
  2. UISearchBar、UISearchContainerViewController、UISearchController
  3. UISplitViewController:左右视图
  4. UUID
  5. UIDevice.current.identifierForVendor.uuidString 是给Vendor标识用户用的,每个设备在所属同一个Vender的应用里,都有相同的值。其中的Vender是指应用提供商,但准确点说,是通过BundleID的反转的前两部分进行匹配

  6. 字符串处理函数
    a. Scanner

    1
    2
    3
    var uint64:UInt64 = 0
    print(Scanner(string: "0xdd").scanHexInt64(&uint64))
    print(uint64)

    b. removeAll

    1
    2
    var str = "a:b:123"
    str.removeAll { return "123".contains($0)} //a:b:

    c. drop

    1
    2
    3
    4
    var str = "aaa:b:a:123"
    let substr = str.drop { (c) -> Bool in
    c == "a"}
    print(substr) //":b:a:123"

    d. filter

    1
    2
    3
    let cast = ["Vivien", "Marlon", "Kim", "Karl"]
    let shortNames = cast.filter { $0.count < 5 }
    print(shortNames) //["Kim", "Karl"]

    e. PersonNameComponents

  7. NSClassFromString: 这个方法判断类是否存在;如果这个函数返回nil,原因有可能为:
    a. swift 写的代码必须将“-ObjC”标志添加到“Other Linker Flags”构建设置
    b. 未加载! 在Other Linker Flags 中添加配置:-all_load 加载所有
    NSStringFromClass
    NSSelectorFromString: 判断某个方法是否存在
    NSProtocolFromString
    //应用场景:第三方库版本变更? 父类中调用子类的函数?

    1
    2
    3
    4
    let selector = NSSelectorFromString("remove")
    if self.responds(to: selector) {
    self.perform(selector)
    }
  8. Range (0..<5)、 ClosedRange (0…5)

  9. Result
  10. ContentHuggingPriority ==> 表示当前的Label的内容不想被拉伸
    ContentCompressionResistancePriority ==> 表示当前的Label的内容不想被收缩
  11. associatedtype

Android 入门

  1. 布局方式
    a. xml布局
    b. 代码布局:LinearLayout

  2. 控件
    a. Button
    b. TextView
    c. ImageView …

  3. 四大组件
    a. 服务
    b. activity
    c. content provider
    d. broadcast receiver

  4. Context

  5. Application

iOS开发能力提升

  1. 关联对象Associated Object,相关函数

    1
    2
    3
    objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
    id _Nullable value, objc_AssociationPolicy policy)

    可能的应用场景:给系统控件增加属性

    1. 关键字:dynamic 动态属性
    2. runtime

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      appInit() {
      let test = class_getInstanceMethod(UIViewController.self, #selector(UIViewController.viewDidLoad))
      let test2 = class_getInstanceMethod(UIViewController.self, #selector(UIViewController.newMethod))
      method_exchangeImplementations(test!, test2!)
      }

      extension UIViewController {
      @objc func newMethod() {
      #if DEBUG
      DLog("\(type(of: self)) was loaded by \(type(of: self.parent))")
      #endif

      self.newMethod()
      }
      }
    3. struct和class之间的抉择:
      a. 默认选struct: 拷贝赋值型,影响相对较小
      b. 与OC交互时,需要使用class型
      c. 需要区分不同实例的时候,使用class(操作符===)

    4. KVO/KVC/Keypath/NSKeyValueObservation !!!

    5. 单例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      //通用单例
      class Singleton {
      static let sharedInstance = Singleton()
      }

      //如果初始化的时候,需要进行其他操作
      class Singleton {
      static let sharedInstance: Singleton = {
      let instance = Singleton()
      // setup code
      return instance
      }()
      }
    6. 自动打包 fastlane

踩坑记录

alamofire验证证书

故障现象:xcode log日志显示如下错误,并且相关请求出错

1
2
3
4
5
6
7
8
9
2019-07-17 11:38:19.396278+0800 Homecare[10181:3941027] CredStore - performQuery - Error copying matching creds.  Error=-25300, query={
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = htps;
"r_Attributes" = 1;
sdmn = Users;
srvr = "cloud.demo.cn";
sync = syna;
}

原因:ios系统验证网站的证书出错 (ps:不过不知道什么原因,这个故障是偶现的^)
处理:无条件信任我们自己的服务器,不需要进行证书校验

1
2
3
4
5
6
7
8
9
10
11
12
init() {
//关闭认证
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
configuration.urlCredentialStorage = nil

let policies: [String: ServerTrustPolicy] = [
"cloud.demo.cn": .disableEvaluation
]

super.init(configuration: configuration,serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
}

手动添加的自动布局代码不生效

1
2
3
4
5
6
7
let subView = UIView()
let left = NSLayoutConstraint(item: videoView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
let right = NSLayoutConstraint(item: videoView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0)
let top = NSLayoutConstraint(item: videoView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0)
let bottom = NSLayoutConstraint(item: videoView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
let constraints = [left, right, top, bottom]
NSLayoutConstraint.activate(constraints)

故障现象:运行的时候发现subview自动生成了midx=0,midy=0,height=0,width=0的约束代码
解决方案:关掉自动生成约束代码的功能

1
2
3
let subView = UIView()
subview.translatesAutoresizingMaskIntoConstraints = false
.......

ATS报错

故障现象:
app发送https://cloud.fanmiot.cn/rest/things请求,结果返回ats报错,日志如下:

1
2
3
4
2019-07-23 09:26:33.044872+0800 Homecare[5571:122357] Task <5547B0FE-DA52-400E-AF8E-D97453C9F2B0>.<1> finished with error - code: -1022
2019-07-23 09:26:33.046236+0800 Homecare[5571:122356] Task <5547B0FE-DA52-400E-AF8E-D97453C9F2B0>.<1> load failed with error Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection., NSErrorFailingURLStringKey=http://undefined/rest/things, NSErrorFailingURLKey=http://undefined/rest/things, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <5547B0FE-DA52-400E-AF8E-D97453C9F2B0>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <5547B0FE-DA52-400E-AF8E-D97453C9F2B0>.<1>, NSUnderlyingError=0x600003f18b10 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "(null)"}} [-1022]

故障原因:
https://cloud.fanmiot.cn/rest/things不可达时,服务器端自动跳转到了http://undefined/rest/things页面……

视图不显示

故障现象:

1
2
3
4
5
6
7
8
class aViewController {
override func viewDidLoad() {
super.viewDidLoad()

let vc = bTabelViewController()
view.addsubView(vc.view)
}
}

问题原因:
vc是函数内的局部变量,函数执行结束就被释放掉了;bTabelViewController的cell是根据数据动态生成的;而这些数据也随着vc的释放而消失了……
所以需要将let vc = bTabelViewController() 移到函数外

在地图上画线,产生偏移

问题代码:

1
2
3
4
let startPoint = mapView.convert(start.coordinate, toPointTo: mapView)
let endPoint = mapView.convert(end.coordinate, toPointTo: mapView)
......
self.view.layer.addSublayer(lineLayer)

问题分析:
将经纬度转成view坐标是以mapview为参考,实际作用于view;所以当view和mapview不重合时就出现问题了,修正后代码:

1
2
3
4
let startPoint = mapView.convert(start.coordinate, toPointTo: view)
let endPoint = mapView.convert(end.coordinate, toPointTo: view)
......
self.view.layer.addSublayer(lineLayer)

ios 导航栏

将导航栏设置为透明

1
2
3
4
5
6
   //背景设置为空
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
//去除横线
self.navigationController?.navigationBar.shadowImage = UIImage()
//设置为透明
self.navigationController?.navigationBar.isTranslucent = true

导航栏的颜色随着滚动条上翻渐渐从透明变成不透明
此时注意,一定需要将istranslucent设为true,不然不管怎么改alpha值,导航栏都会有背景色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func scrollViewDidScroll(_ scrollView: UIScrollView) {

let offset = scrollView.contentOffset.y
//大标题模式下的数值
if offset <= -88 {
naviAlpha = 0
} else if offset < -22 {
naviAlpha = (offset+88)/64
} else {
naviAlpha = 1
}
let image = UIImage.imageWithColor(Colors.naviBackground.withAlphaComponent(naviAlpha))
navigationController?.navigationBar.setBackgroundImage(image, for: .default)
}

,