URL
URL的组成部分只能使用:
- 26个英语字母(包括大写和小写)
- 10个阿拉伯数字
- 连词号(-)
- 句点(.)
- 下划线(_)
此外还有18个保留字,只能在指定的位置出现,如果要在其他位置出现,就必须要对齐进行编码:
!
:%21#
:%23 锚点$
:%24&
:%26 分隔多个查询参数'
:%27(
:%28)
:%29*
:%2A+
:%2B,
:%2C/
:%2F:
:%3A 分隔协议和主机;
:%3B=
:%3D?
:%3F 分隔路径和查询参数@
:%40[
:%5B]
:%5D
URL编码是ASCII编码,而不是Unicode。
- ASCII的局限在于只能显示26个基本拉丁字母、阿拉伯数字和英式标点符号,因此只能用于显示现代美国英语
URL格式
1
https://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#anchor
协议(scheme)
https://
浏览器请求服务器资源的方法
主机\域名(host)
www.example.com
资源所在的网站名或服务器的名字
端口(port)
80
(默认为80)同一个域名下面可能同时包含多个网站,它们之间通过端口区分
路径(path)
/path/index.html
资源在网站的位置
查询参数(parameter)
?key1=value1&key2=value
提供给服务器的额外信息。参数的位置是在路径后面,两者之间使用
?
分隔。查询参数可以有一组或多组。
每组参数以
key=value
的形式表示。 多组参数之间使用
&
连接。锚点(anchor)
#anchor
网页内部的定位点。锚点名称通过网页元素的
id
属性命名
Calendar
1 | Calendar(identifier: .gregorian) // 公历,最常使用 |
1 | //2000-01-1 00:00:00 |
calendar.range( of: .day, in: .month, for: baseDate)?.count
- baseDate所在月份的总天数
calendar.date(from: calendar.dateComponents([.year, .month], from: baseDate))
- baseDate所在月的第一天
calendar.component(.weekday, from: firstDayOfMonth)
- firstDayOfMonth所在一周的第几天
UIStackView的inset
1 | stackView.isLayoutMarginsRelativeArrangement = true |
自定义UIStackView的spacing
1 | 嵌套stackView //iOS11之前 |
一个string是否包含另外一个string
1 | contains(_:) |
两个string是否相等
1 | == |
pod版本限制
pod 'FLEX', '~> 2.2.1'
:版本会维持在2.2.X
的最大版本(2.2.99
)
1 | logical operators: |
绘制圆角矩形
1 | let placeholderImage = UIGraphicsImageRenderer(bounds: CGRect(origin: .zero, size: CGSize(widthHeight: 50))).image { context in |
在viewDidDisappear的时候可以判断是否是被pop
若navigationController==nil,说明被pop掉
绘制image时,指定的frame和生成的image会有误差
systemLayoutSizeFitting
在调用systemLayoutSizeFitting计算view的高度之前需要先指定view中label的preferredMaxLayoutWidth
UIStackView
AutoLayout
- stackView会自动管理布局
属性
Distribution:延轴方向分布
-
- 若stackView大小确定:子view会被拉伸或压缩来填充整个stackView
- 若stackView大小不确定:需要每个子view大小确定,stackView会调整自身大小以适应子view
-
- 所有子view一样大
-
- 根据子view的intrinsic content size的比例调整大小,以适应stackView
-
- 每个子view的间距相等
- 当子view会超出stackView时,会根据子view的 compression resistance priority优先级压缩
-
- 每个子view的中心距离相等
- 当子view会超出stackView时,会根据子view的 compression resistance priority优先级压缩
Alignment:垂直轴方向分布
isLayoutMarginsRelativeArrangement
设置边距
1
2stackView.isLayoutMarginsRelativeArrangement = true
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)优点:
简单
可以设置动画
1
2
3
4stackView.directionalLayoutMargins.leading = 0
UIView.animate(withDuration: 0.3) {
stackView.layoutIfNeeded()
}
setCustomSpacing(_:after:)
自定义间距
1
2
3
4vStackView.addArrangedSubview(movieLabel)
vStackView.addArrangedSubview(quoteLabel)
vStackView.setCustomSpacing(10, after: quoteLabel) // <1>
vStackView.addArrangedSubview(authorLabel)iOS11之后才能用,在11之前只能用嵌套stackView
只有在将quoteLabel加入stackView之后再设置间距才会生效
若将quoteLabel设为隐藏,它后面的间距也会隐藏
设备及其在竖/横屏下的宽高是regular还是compact
Device | Portrait orientation | Landscape orientation |
---|---|---|
12.9” iPad Pro | Regular width, regular height | Regular width, regular height |
11” iPad Pro | Regular width, regular height | Regular width, regular height |
10.5” iPad Pro | Regular width, regular height | Regular width, regular height |
9.7” iPad | Regular width, regular height | Regular width, regular height |
7.9” iPad mini 4 | Regular width, regular height | Regular width, regular height |
iPhone XS Max | Compact width, regular height | Regular width, compact height |
iPhone XS | Compact width, regular height | Compact width, compact height |
iPhone XR | Compact width, regular height | Regular width, compact height |
iPhone X | Compact width, regular height | Compact width, compact height |
iPhone 8 Plus | Compact width, regular height | Regular width, compact height |
iPhone 8 | Compact width, regular height | Compact width, compact height |
iPhone 7 Plus | Compact width, regular height | Regular width, compact height |
iPhone 7 | Compact width, regular height | Compact width, compact height |
iPhone 6s Plus | Compact width, regular height | Regular width, compact height |
iPhone 6s | Compact width, regular height | Compact width, compact height |
iPhone SE | Compact width, regular height | Compact width, compact height |
UIScrollView
contentSize
scrollView内容的大小
contentOffset
contentSize的origin 和 frame的origin的偏移
当scrollview向下拉时,offsetY为负数;当上拉时,offsetY不断增大,当越过原点后会变成正数。
contentInset
contentview相对于scrollview的边距
contentInset和adjustedContentInset
iOS11提出了safeArea的概念,帮助scrollView放在屏幕的可视部分
在iOS 11中决定tableView的内容与边缘距离的是adjustedContentInset属性,而不是contentInset。
contentsize是scrollView的滚动范围
contentinset是为scrollView增加额外的滚动区域
contentoffset是scrollView的滚动位置
safeAreaInsets是view到safeArea的距离
adjustedContentInset表示contentView.frame.origin偏移了scrollview.frame.origin多少。是系统计算得来的,计算方式由
contentInsetAdjustmentBehavior
决定。有以下几种计算方式:ScrollableAxes
- 在可滚动方向上:adjustedContentInset = safeAreaInset + contentInset,
- 在不可滚动方向上:adjustedContentInset = contentInset;
Automatic:
scrollview在一个automaticallyAdjustsScrollViewInsets = YES的controller上,并且这个Controller包含在一个navigation controller中:
在top & bottom上的 adjustedContentInset = safeAreaInset + contentInset,不管是否滚动。
其他情况下:
与
ScrollableAxes
相同
Never
- adjustedContentInset = contentInset
Always
- adjustedContentInset = safeAreaInset + contentInset
ContentInsetAdjustmentBehavior
如上
automaticallyAdjustsScrollIndicatorInsets
是否自动调整滚动条的insets
translatesAutoresizingMaskIntoConstraints
默认值是true
若值是true时,系统会自动创建和view的 autoresizing mask完全相同的约束,并且这些约束包含了view的size、position。因此此时在添加额外的约束时,会有布局冲突。
所以在使用Auto Layout来进行动态布局时,需要将该属性设为false。
note:在使用snapKit布局时,snapkit会自动将该值设为false。但是若没有对一个view使用snp布局,该值仍然保持为true。
setContentCompressionResistancePriority &setContentHuggingPriority
- setContentCompressionResistancePriority:抗压缩性,适用于布局空间比view的size要小
- setContentHuggingPriority: 抗拉伸性,适用于布局空间比view的size要大
监听键盘事件
1 | public class let keyboardWillShowNotification: NSNotification.Name |
获取键盘信息
1 | public class let keyboardFrameBeginUserInfoKey: String //键盘动画起始时的frame |
示例
1 | NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification) |
UIModalPresentationStyle和UIModalTransitionStyle
1 | enum UIModalPresentationStyle : Int { |
1 | enum UIModalTransitionStyle : Int { |
用UIViewControllerTransitioningDelegate显示蒙层
1 | extension VC: UIViewControllerTransitioningDelegate { |
内存泄漏
自己监听自己
self.rx.observe(CGRect.self, "bounds").map({ $0 ?? .zero }).distinctUntilChanged()
解决方法:
contentView.rx.observe(CGRect.self, "bounds").map({ $0 ?? .zero }).distinctUntilChanged()
viewModel中的监听被view的bag管理而不是reuseBag
viewModel.relayObject.oberve()....disposed(by bag)
解决方法:
viewModel.relayObject.oberve()....disposed(by reuseBag)
监听View大小
bounds
scrollView.rx.observe(CGRect.self, "bounds").map({ $0 ?? .zero }).distinctUntilChanged()
scrolView的contentSize
scrollView.rx.observe(CGSize.self, "contentSize").map({ $0 ?? .zero }).distinctUntilChanged()
音频
AVQueuePlayer
可以自动管理多个网络音频
AVQueuePlayer在播放完成之后会把palyItem从队列中删除,因此在播放时要判断playItem是否为空,
若是空需要调整playItem的播放进度,然后将item重新加入queue中
1 | if player.items().isEmpty { |
监听加载状态
1 | // AVPlayerItem状态改变 |
监听播放完成(播放完成,重新将进度设为第一帧)
1 | // 播放完成 |
监听播放失败
1 | // 播放失败 |
在OC和swift中的Int64类型转换
swift中的int传给OC后,OC会将其转为NSNumber类型,在OC中若想使用int类型,就需要调用NSNumber的方法将其转为Int类型
代码注意
- 分母的变量要用
.clamped(to: 1.0...)
保证其值大于1 - static的全局变量会自动转成lazy
- 将计算属性
var a { ... }
转为var a = { ... }()
可使得闭包内代码只运行一次
会导致source kit崩溃的原因
rx.tapGuesture
用在非UIButton上
移除当前显示的controller
1 | strongSelf.dismiss(animated: true, completion: nil) |
1 | current.navigationController?.viewControllers = current.navigationController?.viewControllers.filter { |
dismiss
func dismiss(animated: Bool, completion: (() -> Void)?)
Dismisses the view controller that was presented modally by the view controller.
A -> B -> C
C.dismiss : 移除C
B.dismiss : 移除C
A.dismiss : 移除B,C
PresentedViewController 与 PresentingViewController
假设Controller A通过present跳到Controller B,B又通过present跳到Controller C,那么:
B.presentedViewController
就是 C
B.presentingViewController
就是 A
iOS权限查询
1 | func permissions() { |
通知权限
1 | UNUserNotificationCenter.current().getNotificationSettings { [weak self] (settings) in |
保证有相机权限
1 | _ = AVCaptureDevice.rx.cameraAuthorizedOrShowAlertIfNeeded |
跳到设置界面
1 | if let url = URL(string: UIApplication.openSettingsURLString) { |
API获取的整数转为枚举
1 | enum Grade: Int { |
根据不同的评级使用不同的图片
1 | extension Grade { |
图片旋转
仿射变换:是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。(来自百度百科)
在iOS中表现为平移、旋转、缩放等。
1 | iamgeView.transfrom = CGAffineTransform(rotationAngle: CGFloat.pi * (-15.0 / 180.0)) |
根据不同的情景使用不同的布局
一、布局的offset不同
1 | import SnapKit |
二、布局的参考对象不同
1 | import SnapKit |
rx只监听一次
1 | .take(1) |
定时器
1 | [self.timer setFireDate:[NSDate distantPast]] //开始计时器 |
1 | final class TimerHelper { |
从gerrit上download已经revert的提交
复制Cherry Pick URL,在本地分支运行,
若有冲突,解决冲突后,commit(message直接复制被revert的信息,包含changeID)
名词
keypoint
:知识点question
:题目
时间
1 | let timeString: String = DateUtils.dateString(from: .init(timeIntervalSince1970: someTime), withDateFormat: "yyyy年MM月dd日HH时mm分") |
把一个view包装为左侧有2pixl的新view
1 | view.leo.wrapped(left: 2.0) |
获取API错误返回的状态码
1 | UpdateUserInfoAPI.rx.response() |
frog
1 | ProfileFrogConstants.eventCameraPermission.leo.frog(parameters: ["permission": "true"]) |
判断是否是表情
1 | private extension String { |
判断字符是否是汉字
1 | if character >= "\u{4e00}" && character <= "\u{9fa5}" { |
日期扩展
1 | extension Date { |
富文本
1 | let price = NSMutableAttributedString( |
计算文字高度
boundingRect
1 | let size = labelText.boundingRect( |
1 | extension String { |
systemLayoutSizeFitting
1 | func layoutSize(in context: layoutContext) -> layoutSize { |
对齐设置
方法一:使用snp
1 | buttonC.snp.makeConstraints { make in |
方法二:直接设置
1 | buttonC.translatesAutoresizingMaskIntoConstraints = false |
设置根视图
在SceneDelegate.swift文件中的scene()函数中添加
1 | let nav = UINavigationController(rootViewController: ViewController()) |
Podfile使用
- 安装cocoapod
- 在项目目录下创建
Podfile
文件 - 在文件中添加响应的依赖
- 允许
pod install
分割线
1 | private let line = UIView() //存储属性 |
或者使用SeparatorCollectionViewLayout(),在init()中传递给父类init()方法
监听某个存储属性
给属性增加
@ObservableProperty
在界面中使用
instance.$property.subscribe
来监听1
2
3
4
5
6
7MyLogger.shared.$messages
.observeOn(MainScheduler.instance) //把线程切换到主线程
.subscribe(onNext: { [weak self] in
guard let strongSelf = self else { return }
//通过$0获取监听的属性
})
.disposed(by: bag)
也可以给属性包装为BehaviorRelay
,用.accept
添加值并通知监听者,用rx的.subscribe
来处理响应
viewDidLoad的功能切分
- setupSubviews()
- setupLayout()
- setupActions()
点击背景隐藏键盘
1 | collectionView.keyboardDismissMode = .onDrag |
方法二:
创建一个手势 private let tapBackground = UITapGestureRecognizer()
将手势添加到view中:view.addGestureRecognizer(tapBackground)
设置手势点击完成编辑:
1
2
3
4
5
6tapBackground.rx.event
.subscribe(onNext: { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.view.endEditing(true)
})
.disposed(by: bag)
搜索框
用textField
1 | private let textField = UITextField() |