当前位置: 首页 > 工具软件 > canDB.swift > 使用案例 >

Swift Tips(18-32)

曹涵润
2023-12-01

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: ", "))
}
}
}

当然,自定义类型参数和范型参数也是可以的。


  1. a-zA-Z0-9#%&()+ ,\-./:;=?\[\]^_|*\ ‘\” ↩︎

 类似资料: