UIKitで、たまに遭遇するけど忘れがちな部分をメモっておきます。
ViewやCell等でControlイベントが取れない場合のチェックリスト
- イベントを取りたいViewの
isInteractionEnabled
がtrueになってるか? - イベントを取りたいViewの
alpha
が0になっていないか?-
backgroundColor = .clear
にして、UIView.alphaは0より大きく設定する
-
-
イベントを取りたいViewの上に、透明系のViewが被さってて、それがイベントを奪っていないか?
- storyboardでUIViewを作成すると、デフォルトで
isInteractionEnabled = true
になっているので、注意! - storyboardでなくても、コードで追加したUIViewも同様なので、注意!
- イベントを透過させたいUIViewは、
isInteractionEnabled
をfalse
にする
- storyboardでUIViewを作成すると、デフォルトで
- 親のViewの大きさが意図せず小さくなっていないか?
- 親Viewからはみ出てる領域では、tapなどのイベントが取れない
- AutoLayoutの制約が足りてないとwidthやheightが0になる事がある
- そもそも
override func hitTest()
は取れてるか?pointInside()
- 参考: https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events
UICollectionViewのselection管理
UICollectionViewやUITableViewには、セルの選択状態を管理する機能が備わっています。
- isSelectedは、collectionView側で管理されている
- スクロールでCellが見えなくなり、(Cellが再利用され)、スクロールで再度表示した際には
cell.isSelected
が復元されている -
collectionView.indexPathsForSelectedItems()
で、選択状態のindexPathの配列が取れる
Cellは画面外にスクロールすると再利用されるのに、選択状態は見えない範囲も全て管理してるのが、意外といえば意外でした。
- 参考
UITextFieldのshouldChangeCharactersInをRangeで扱う
- NSRangeはNSStringに対応している。(UTF-16)
- RangeはStringに対応する。(Unicode Scalar + 正規化)
- 無理に使うと、絵文字を入れた時等にクラッシュする事がある。(特に絵文字バリエーションシーケンスの扱いが違う)
- 参考
- (なぜここだけNSRangeのまま残ってしまってるのか…)
public func textField(_ textField: UITextField, shouldChangeCharactersIn nsRange: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text,
let range = Range(nsRange, in: text) else {
return false
}
let newText = text.replacingCharacters(in: range, with: string)
/* ... */
}
NavigationBarをスクロールの方向で隠す
比較的簡単でTwitterっぽい自然な感じの動きにしてみました。
バウンスに反応しないようisTracking
を見ています。
class MyViewController {
@IBOutlet private var collectionView: UICollectionView!
private var lastContentOffset: CGPoint = .zero
private var lastTracking = false
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
}
// 画面遷移前には表示に戻しておく
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: true)
}
}
extension MyViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let dy = scrollView.contentOffset.y - lastContentOffset.y
lastContentOffset = scrollView.contentOffset
let didTouchUp = lastTracking && !scrollView.isTracking
lastTracking = scrollView.isTracking
// 下にスクロールしたら隠す
if scrollView.isTracking && dy > 5 {
navigationController?.setNavigationBarHidden(true, animated: true)
}
// 指を離したタイミングで上にスクロールしていたら表示
if didTouchUp && dy < -10 {
navigationController?.setNavigationBarHidden(false, animated: true)
}
}
}
Darkモード対応やアイコンなど
- まずLight/Darkで色名を共通化し、Asset Colorで定義する (自動的に切り替わる)
- コードからもAssetの色を使う
- 単色のIconのLight/Darkの変化や色バリエーションは、コードでtintColorで設定する(管理するIconの枚数が減る)
- Light/Darkでイメージを変える場合は、Dark Appearanceに登録する
- UIColorの置き換えが無理な場合は、
UIView.traitCollectionDidChange()
で切り替えタイミングを検出できるので、そこで更新する- 現在のモードは、
UITraitCollection.current.userInterfaceStyle
で判別できる
- 現在のモードは、