Swift Tips(18-32)
Posted 颐和园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift Tips(18-32)相关的知识,希望对你有一定的参考价值。
18. 软键盘添加 Done button
extension UITextField {
func addDoneToolbar(onDone: (target: Any, action: Selector)? = nil) {
var doneButton: UIBarButtonItem! if let onDone = onDone {
doneButton = UIBarButtonItem(title: localizedString(with: “cosmos_common_done”), style: .done, target: onDone.target, action: onDone.action)
} else {
doneButton = UIBarButtonItem(title: localizedString(with:
“cosmos_common_done”), style: .done, target: self, action: #selector(resignFirstResponder))
}
let toolbar = UIToolbar() toolbar.barStyle = .default toolbar.items = [
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil),
doneButton ]
toolbar.sizeToFit()
inputAccessoryView = toolbar }
}
用法:
accountNumberField.addDoneToolbar()
或者:
viewController.accountNumberInputView?.addDoneToolbar(onDone: (target: self, action: #selector(doneTap)))
19. Textfield 不允许粘贴特殊字符
修改 InputValidationHelper 类中的校验逻辑。这里被进行了限制,比如允许用户输
入数字、空格、字母和标点符号:
func isValidNicknameInput(string: String?, maxLength: Int) -> Bool {
guard let inputText = string, !inputText.isEmpty else { return true }
guard inputText.count <= maxLength else { return false }
let ret = testRegularExpression(pattern:
ValidationPattern.nickName.pattern, testString: inputText) debugPrint(ret)
return ret
}
20. 无法校验单引号和双引号
无论正则表达式怎么写,你都无法在 swift 里成功验证单引号和双引号。是因为
Xcode 里的单引号和双引号和 ios 里的单引号、双引号不是一个字符。 例如:"1*$"
在 iphone 上这两个符号实际上是:‘ 和 “,Unicode 码分别是: \\U8216 和 \\U8220 在 lldb 中打印这两个符号的 unicode 的方法如下:
(lldb) ex inputText.unicodeScalars
(String.UnicodeScalarView)
R
4
=
g
u
t
s
=
o
b
j
e
c
t
=
(
c
o
u
n
t
A
n
d
F
l
a
g
s
B
i
t
s
=
1
,
o
b
j
e
c
t
=
0
x
500060000286
c
620
)
(
l
l
d
b
)
p
o
I
n
t
(
R4 = { _guts = { _object = (_countAndFlagsBits = 1, _object = 0x500060000286c620) } } (lldb) po Int(
R4=guts=object=(countAndFlagsBits=1,object=0x500060000286c620)(lldb)poInt(R4[KaTeX parse error: Expected 'EOF', got '#' at position 55: …: "^[a-zA-Z0-9#̲%&()+ ,\\\\-./:;=…"
21. 如何将 gif 添加到 Assets.xcassets
1、将 xxx.gif 文件拖进 Assets.xcassets。这时 Xcode 不能正确将 gif 识别为图片,而是当成二进制文件,因此不会自动添加 1x、2x、3x 图。
2、Show in Finder,你会发现多了一个 xxx.dataset 的文件夹,将它改成xxx.imageset。
3、打开下面的 Contents.json 文件,修改为:
{
"images" : [
{
"idiom" : "universal",
"scale" : "2x",
"filename" : ""
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : ""
}
],
"info" : {
"author" : "xcode",
"version" : "1"
}
}
这时在 Xcode 中,你会发现可以将 xxx.gif 分配给 2x 图和 3x 图了。
注意这样做你将无法用 NSDataAsset 加载 .gif,因为 NSDataAsset 只能加载二进制,不能加载图片。
22. 通过修改 transform 实现的屏幕适配方案。
这可能是最简单的 iPad 屏幕适配方案。只需在 viewDidLoad 方法设置 view.transform 即可:
let SCREEN_WIDTH = UIScreen.main.bounds.size.width
let SCREEN_HEIGHT = UIScreen.main.bounds.size.height
let DESIGN_SCREEN_WIDTH = CGFloat(834)
let DESIGN_SCREEN_HEIGHT = CGFloat(1194)
let SCREEN_SCALE_X = SCREEN_WIDTH / DESIGN_SCREEN_WIDTH
let SCREEN_SCALE_Y = SCREEN_HEIGHT / DESIGN_SCREEN_HEIGHT
let SCREEN_SCALE_BETTER = min(SCREEN_SCALE_Y, SCREEN_SCALE_X)
let SCREEN_WIDTH_BETTER = SCREEN_WIDTH / SCREEN_SCALE_BETTER
let SCREEN_HEIGHT_BETTER = SCREEN_HEIGHT / SCREEN_SCALE_BETTER
override func viewDidLoad() {
view.transform = CGAffineTransform(scaleX: SCREEN_SCALE_BETTER, y: SCREEN_SCALE_BETTER)
这里,DESIGN_SCREEN_WIDTH 和 DESIGN_SCREEN_HEIGHT 是设计图中 iPad 的宽和高。SCREEN_WIDTH_BETTER 和 SCREEN_HEIGHT_BETTER 常量也是有必要的,因为如果你在进行 translate 动画时,凡是涉及到屏幕宽高的移动,都要用这两个常量代替 SCREEN_WIDTH 和 SCREEN_HEIGHT,避免移动的像素不是你想要的,比如在下面的代码中,你必须要 SCREEN_WIDTH_BETTER 替代 SCREEN_WIDTH :
let x = -SCREEN_WIDTH_BETTER/2
view.move(x: x, y: 0, duration: 1) // 往左移动屏幕一半的距离
如果你仍然用 SCREEN_WIDTH ,你会发现在小于 DESIGN_SCREEN_WIDTH 宽度的屏幕上,实际移动的距离会不到屏幕的一半。
还有一点需要注意,使用这种方案有一个缺点,就是一旦view controller 被修改 transform 后就不能再充当 rootViewController 了(但是可以在外面套一层 navigation controller),否则在present/dismiss modal view 时会出现一些问题。
注意,如果有某个view需要保持原有 scale 不被缩放,则只需将 scale 设置为 SCREEN_SCALE_BETTER 的倒数即可:
backImageView.transform = CGAffineTransform(scaleX: 1/SCREEN_SCALE_BETTER, y: 1/SCREEN_SCALE_BETTER)
23. insertSubview 的正确用法?
如果要将 imageView2 插入到 imageView1 之前,只需将 index 设置得比它小或者相等(后插入的位于下面) 即可,但不能为负数(否则不显示):
myView.insertSubview(imageView1, at: 0)
myView.insertSubview(imageView2, at: 0)
要将 imageView2 放在 imageView1 之上, 将 index 设置为比它大即可:
myView.insertSubview(imageView1, at: 0)
myView.insertSubview(imageView2, at: 1)
insertSubview(_: belowSubview:) 方法只能设置属于同一级 subview 之间的前后关系,比如:
contentView.insertSubview(shampooBottle, belowSubview: hairMaskBox)
其中,shampooBottle 和 hairMaskBox 必须是兄弟关系。
24. 为什么拿到的 frame 总是不对?
不要在 viewDidLoad/DidAppear 等方法中拿 view 的 frame。view 的 frame 只有在 viewDidLayoutSubviews 之后才会正确。但是 viewDidLayoutSubviews 方法会调用多次(view 的 frame 被改变,或者屏幕旋转都会触发)。
因此,当你需要在 viewDidLoad/DidAppear 方法中获取 view 的 frame 时,可以先调用这两句:
view.setNeedsLayout()
view.layoutIfNeeded()
这会触发 frame 重新计算。
25. 定义 Outlet collection 时出错 ‘weak’ may only be applied to class and class-bound protocol types
定义 outlet collection 时不能使用 weak:
@IBOutlet var bottleViews: [ChooseBottleColorView]!
将 weak 删除即可。
26. 为什么有时候 weak self 之后还要 guard let self=self?
有时候看到这样代码:
delay(seconds:Constants.beginAnimationDelay) {[weak self] in
guard let self = self else { return }
self.view.actorsFadeIn(inclusive: [self.animationView, self.rechooseView],duration: duration)
}
因为不这样写的话,会导致代码写起来很啰嗦:
delay(seconds:Constants.beginAnimationDelay) {[weak self] in
self?.chooseColorAnimationView.doAnimation(.begining, duration: duration)
if let v1 = self?.animationView,let v2 = self?.chooseView {
self?.view.actorsFadeIn(inclusive:[v1, v2],duration: duration)
}
}
但是用了 guard 一句后,那句 if 判断就完全不需要了。
27. 交换数组元素的位置
var productArr = productTypes
productArr.swapAt(1, 2)
28. 为什么有时候向故事板中的 view 不能添加约束?
Size 面板中,将 Layout设置为 Inferred(Constraints)。
29. 获取数组元素的下标
currentIndex = bottleViews.firstIndex(of: sender)
但是得到的是一个 optional。
30. 自定义 view 出现:Designables Error
Editor->Debug Selected Views 进行调试。
31. Debug custom view 时出现错误:Runtime: iOS 14.5 (18E182) - DeviceType: IBSimDeviceTypeiPad2x
进入 ~/Library/Logs/DiagnosticReports 目录,查看 IBDesignablesAgent-iOS 开头的日志。
错误定位于这里:
titleLabel.font = .font(type: .apercuRegular, size: 18)
一般原因都是 custom view 的 init() 方法中有错误代码,比如加载不了图片资源(IB bug),或者使用了自定义字体等。原因如下:
IB 在渲染 costom view 时,使用一个 DesignablesAgent 的组件。这个组件在渲染时不会加载 App 的 bundle(main bundle)。因此你在 init 方法(不管哪个init 方法)中,如果有代码加载了图片资源或custom字体,那将返回 nil,正常情况下不会有问题,因为无论是 UIImage 的 image 属性,还是 UILabel 的 font 属性,都是 optional 的。而且哪怕是 init 时加载不到,但在 updateDisplay 时还是会重新加载(如果你通过 IB 设置了这些属性),所以图片、字体还是可以正常显示的。
但是如果你对这些资源进行了强制解包(使用! 进行强制解包),比如:
static func font(type: FontType, size: Float) -> UIFont {
UIFont(name: type.rawValue, size: CGFloat(size))!
}
当然就会导致 Designables Agent crashed。因此需要将强制解包修改为:
UIFont(name: type.rawValue, size: CGFloat(size)) ?? UIFont.systemFont(ofSize: CGFloat(size))
32. 字符串插入参数
Swift 5 新出现的字符串插入系统,允许你进行字符串插入时使用命名参数,比如:
print("You should follow me on Twitter: \\(twitter: "twostraws").")
这里的 twitter: 就是一个插入参数。你可以扩展字符串的 StringInterpolation(它是结构体 DefaultStringInpterpolation 的别名):
extension String.StringInterpolation {
mutating func appendInterpolation(twitter: String) {
appendLiteral("<a href=“https://twitter.com/(twitter)”>@(twitter)")
}
多个参数也是支持的:
mutating func appendInterpolation(format value: Int, using style: NumberFormatter.Style) {
let formatter = NumberFormatter()
formatter.numberStyle = style
if let result = formatter.string(from: value as NSNumber) {
appendLiteral(result)
}
}
调用的时候:
print(“Hi, I’m (format: age, using: .spellOut).”)
甚至可以使用闭包参数:
extension String.StringInterpolation {
mutating func appendInterpolation(_ values: [String], empty defaultValue: @autoclosure () -> String) {
if values.count == 0 {
appendLiteral(defaultValue())
} else {
appendLiteral(values.joined(separator: ", "))
}
}
}
当然,自定义类型参数和范型参数也是可以的。
a-zA-Z0-9#%&()+ ,\\-./:;=?\\[\\]^_|*\\ ‘\\” ↩︎
以上是关于Swift Tips(18-32)的主要内容,如果未能解决你的问题,请参考以下文章
如何将这个 Objective-C 代码片段写入 Swift?