20210612のSwiftに関する記事は7件です。

iOS15でのUIKitの変更箇所をまとめた。

はじめに どうも@kaneko77です。 WWDC2021が今週ありました。 月曜日の26時からだった為、定時で上がって速攻寝て25時半に起きてWWDCに備えていました。 ※WWDCのEvent参照 今回はハードウェアの発表がなかったのは残念でしたが、割と面白内容になっていて早起きした甲斐あったな〜と思いました。 発表が終わった後にBeta版入れて色々試したのでその中でiOS15の変更点を今回は共有していきたいと思います。 特に私が興味がある内容に焦点を当てて記事を書いてます。 ※SwiftUIについては触れないです。 ※まだまだ新しい技術なので情報がネットに出回っていなく共有できない箇所がありますのでそちらご了承いただけますと幸いです。 こんな方対象 iOS15で何ができるようになったか知りたい人 iOSエンジニアの方 環境 Xcode: Version 13.0 beta (13A5154h) iOSVersion: iOS15 Swift: 5.5 参考にしたサイト https://blog.prototypr.io/new-in-ios-15-for-product-designers-design-engineers-aa504e3374f8 https://github.com/apple/swift/blob/main/CHANGELOG.md https://alejandromp.com/blog/wwdc21-notes/ https://developer.apple.com/videos/play/wwdc2021/10059/ 目次 UISheetPresentationControllerで下からピョコってするやつめちゃくちゃ楽になった! UIButtonで色々できるようになった UIMenuにサブビューが追加された シンボルが追加された! そのほか 紹介 UISheetPresentationControllerで下からピョコってするやつ楽になった! こちらは半Modalを簡単に実装できる物になります。 ライブラリだとこちらをよく使ってました。 以下がUISheetPresentationControllerを使ったmodal表示になります。 こちら参考に作ってます。 最初は半Modalで表示し、ボタンを押下すると普通のModalになるように作っています。 コード まずは簡単に実装したコードを貼ります。 こちら全部コピペしてもらえれば動きます。 ※iOS15からじゃないと使えないです。 表示Type(2種類) 呼び出し元 import UIKit class ViewController: UIViewController { var mainView: MainView! override func loadView() { super.loadView() self.mainView = MainView(frame: .zero) self.view = self.mainView } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.mainView.pushButton.addTarget(self, action: #selector(tapped(_:)),for: .touchUpInside) } // MARK: タッチイベント @objc func tapped(_ sender : Any) { present(ModalViewController.init(), animated: true, completion: nil) } } class MainView: UIView { let hogeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "テストで表示" label.font = .boldSystemFont(ofSize: 32) label.textColor = .black label.textAlignment = .center return label }() let pushButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("「ぴょこ」っと出す", for: .normal) button.setTitleColor(.white, for: .normal) button.backgroundColor = .red button.layer.cornerRadius = 25 return button }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .systemGray setComponent() setConstraint() } private func setComponent(){ [hogeLabel, pushButton].forEach{ addSubview($0) } } private func setConstraint(){ NSLayoutConstraint.activate([ hogeLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50), hogeLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), hogeLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), pushButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -20), pushButton.heightAnchor.constraint(equalToConstant: 50), pushButton.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), pushButton.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 呼び出し側 import UIKit class ModalViewController: UIViewController, UISheetPresentationControllerDelegate, ModalViewDelegate { var mainView: ModalView! var sheetPresentationController: UISheetPresentationController! override func loadView() { super.loadView() self.mainView = ModalView(frame: .zero) self.mainView.delegate = self self.view = self.mainView sheetPresentationController = presentationController as? UISheetPresentationController // modalのサイズ sheetPresentationController.delegate = self sheetPresentationController.selectedDetentIdentifier = .medium // 上部にグラバーを表示する sheetPresentationController.prefersGrabberVisible = true // 高さの定義 sheetPresentationController.detents = [ .medium(), .large() ] // 角丸数値 let cornerRadius = 80 // modalの角丸の数値反映 self.sheetPresentationController.setValue(cornerRadius, forKey: "preferredCornerRadius") } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } // MARK: ModalViewDelegate func onTapExpansionButton() { self.sheetPresentationController.animateChanges { if self.sheetPresentationController.selectedDetentIdentifier == .medium { self.sheetPresentationController.selectedDetentIdentifier = .large } else { self.sheetPresentationController.selectedDetentIdentifier = .medium } } } } protocol ModalViewDelegate: AnyObject { func onTapExpansionButton() } class ModalView: UIView { weak var delegate: ModalViewDelegate? let hugaLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "テストでModalを表示する" label.textColor = .black label.textAlignment = .center return label }() let expansion: UIButton = { let button = UIButton() button.setTitle("拡大・縮小", for: .normal) button.translatesAutoresizingMaskIntoConstraints = false button.setTitleColor(.white, for: .normal) button.backgroundColor = .black button.layer.cornerRadius = 16 return button }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .yellow setComponent() setConstraint() } // MARK: タッチイベント @objc func onTapExpansionButton(_ sender : Any) { delegate?.onTapExpansionButton() } private func setComponent(){ [hugaLabel, expansion].forEach{ addSubview($0) } self.expansion.addTarget(self, action: #selector(onTapExpansionButton(_:)),for: .touchUpInside) } private func setConstraint(){ NSLayoutConstraint.activate([ hugaLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50), hugaLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), hugaLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), expansion.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -50), expansion.heightAnchor.constraint(equalToConstant: 32), expansion.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), expansion.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 説明 重要なのはここ! コメントに書いた内容となっております。 Modalの角丸の数値は自由に変更して遊んでみてください。 // modalのサイズ sheetPresentationController.delegate = self sheetPresentationController.selectedDetentIdentifier = .medium // 上部にグラバーを表示する sheetPresentationController.prefersGrabberVisible = true // 高さの定義 sheetPresentationController.detents = [ .medium(), .large() ] // 角丸数値 let cornerRadius = 80 // modalの角丸の数値反映 self.sheetPresentationController.setValue(cornerRadius, forKey: "preferredCornerRadius") あとはボタンを押下した時の処理 sheetPresentationController.selectedDetentIdentifierで自分の設定した高さを持っています。 その為、こちらを変更すればあらびっくり変わります。 sheetPresentationController.animateChangesはアニメーション処理です func onTapExpansionButton() { self.sheetPresentationController.animateChanges { if self.sheetPresentationController.selectedDetentIdentifier == .medium { self.sheetPresentationController.selectedDetentIdentifier = .large } else { self.sheetPresentationController.selectedDetentIdentifier = .medium } } } UIButtonで色々できるようになった こちら参考 こちらも参考 UIButtonで複数行がサポートされるようになった! これまではUIButtonで複数行のテキストを表示するとなると アトリビュート を使っていたと思います。 しかし今回から楽になっちゃいました✌️ また、角丸などもcornerRadiusで定義せずとも自動でできます。 コード 複数行に対応したボタン let pushButton: UIButton = { // configの定義 var config = UIButton.Configuration.tinted() // 画像の設定 // config.image = UIImage(systemName: "person.2.circle.fill") // 画像どっち側に表示するか (何種類かあります。) // config.imagePlacement = .top config.buttonSize = .large // 角丸を自動で出す (何種類かあります。) config.cornerStyle = .capsule // 背景の設定 config.baseBackgroundColor = .systemGreen let button = UIButton(configuration: config) button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("「ぴょこ」っと出す\nひょっこりはん!!", for: .normal) button.setTitleColor(.white, for: .normal) return button }() 上記のコードを適当な画面に入れることで下記のように表示されると思います。 ちなみに画像などの設定をすると以下のように表示されます? UIButtonでインジケータの表示ができるようになった これまではライブラリなどを入れてログインボタンを押下したらAPIが走ってその際はクルクルさせるみたいなのをやっていましたが、Configでできるようになりました。 実演したのが以下になります。3秒でインジケータが終わるように書いています。 コード こちらshowsActivityIndicatorの ture or false で楽に制御できます。 画像だけクルクル回るのは気にくはない感じがある(ボソッ) // インジケーターの表示 self.mainView.pushButton.configuration?.showsActivityIndicator = true UIMenuにサブビューが追加された 多分UIMenuってIOS13からいますよね? 割と利用シーンがわからず放置していました。ActionSheet使うわってなっていましたが、 サブビュー追加は割と私の中では使ってみたいなと思いました。 ではいきましょう。 元々は2階層のものは以下のような表示でした(iOS14.5) しかしiOSから以下になります。 階層があるものは一発でわかるようになります UIが進化して使いやすくなりました。? コード 適当に作りました。 iOS15になったからと言ってこの階層の設定については..... 何もやることがありません もう一回言います。勝手にOSがやってくれるます。その為 何もやることはありません import UIKit // メニュー表示項目 enum MenuType: String { case A = "アニメ" case B = "食べ物" case C = "電話" case D = "スターウォーズ" case E = "ゴーストバスター" } class TestMenuViewController1: UIViewController { var mainView: TestMenuView1! var selectedMenuType: MenuType = .A override func loadView() { super.loadView() self.mainView = TestMenuView1(frame: .zero) self.view = self.mainView } override func viewDidLoad() { super.viewDidLoad() // UIMenuを設定 self.menuSet() } private func menuSet() { var actions = [UIMenuElement]() [MenuType.A, MenuType.B, MenuType.C].forEach{ element in let action = UIAction( title: element.rawValue, state: self.selectedMenuType == element ? .on : .off, handler: { _ in self.selectedMenuType = element // stateの更新 self.menuSet() }) actions.append(action) } var subMenus = [UIAction]() [MenuType.D, MenuType.E].forEach{ element in let action = UIAction( title: element.rawValue, state: self.selectedMenuType == element ? .on : .off, handler: { _ in self.selectedMenuType = element // stateの更新 self.menuSet() }) subMenus.append(action) } let subMenu = UIMenu(title: "映画", children: subMenus) actions.append(subMenu) // Buttonに対してUIMenuの初期設定 self.mainView.menuButton.menu = UIMenu(title: "", options: .displayInline, children: actions) self.mainView.menuButton.showsMenuAsPrimaryAction = true self.mainView.menuButton.setTitle( self.selectedMenuType.rawValue, for: .normal ) } } class TestMenuView1: UIView { let hogeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "1個目のViewだよ!" label.font = .boldSystemFont(ofSize: 32) label.textColor = .black label.textAlignment = .center return label }() let menuButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("UIMenuを出す", for: .normal) button.setTitleColor(.white, for: .normal) button.backgroundColor = .red button.layer.cornerRadius = 25 return button }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .systemGray setComponent() setConstraint() } private func setComponent(){ [hogeLabel, menuButton].forEach{ addSubview($0) } } private func setConstraint(){ NSLayoutConstraint.activate([ hogeLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50), hogeLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), hogeLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), menuButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -20), menuButton.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), menuButton.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } シンボルが追加された! 新しく600以上シンボルが追加されました。 こちらでDLできます。 そのほか 他にはTabが透明にできるようになった(まだ確認中) TableViewのstyleに新しいものが追加された(まだ確認中) UIColorにsystemBrownというものが追加された茶色かな? 終わりに 多分私がまとめた内容が今回の変更点が全てではないと思います。 しかし主要な部分は抑えられていると思います。 それにしてもUIButtonが大幅に進化しましたね。こちらに全て説明されているので見てみるのも良いと思います。 個人的に下からぴょこっとちっちゃいModalViewがライブラリを使わず楽にできるようになったのは良いと思いました。 iOS15のリリース日はまだ先(予想が2021年秋頃)とのことなので世の中のiOSエンジニアさんたちは余裕を持って対応できると思います。iOS13みたいに死ぬほど作業量が増えるような修正がなくてよかったです。 ここまで読んでいただきありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IOS15でのUIKitの変更箇所をまとめた。

はじめに どうも@kaneko77です。 WWDC2021が今週ありました。 月曜日の26時からだった為、定時で上がって速攻寝て25時半に起きてWWDCに備えていました。 ※WWDCのEvent参照 今回はハードウェアの発表がなかったのは残念でしたが、割と面白内容になっていて早起きした甲斐あったな〜と思いました。 発表が終わった後にBeta版入れて色々試したのでその中でiOS15の変更点を今回は共有していきたいと思います。 特に私が興味がある内容に焦点を当てて記事を書いてます。 ※SwiftUIについては触れないです。 ※まだまだ新しい技術なので情報がネットに出回っていなく共有できない箇所がありますのでそちらご了承いただけますと幸いです。 こんな方対象 iOS15で何ができるようになったか知りたい人 iOSエンジニアの方 環境 Xcode: Version 13.0 beta (13A5154h) iOSVersion: iOS15 Swift: 5.5 参考にしたサイト https://blog.prototypr.io/new-in-ios-15-for-product-designers-design-engineers-aa504e3374f8 https://github.com/apple/swift/blob/main/CHANGELOG.md https://alejandromp.com/blog/wwdc21-notes/ https://developer.apple.com/videos/play/wwdc2021/10059/ 目次 UISheetPresentationControllerで下からピョコってするやつめちゃくちゃ楽になった! UIButtonで色々できるようになった UIMenuにサブビューが追加された シンボルが追加された! そのほか 紹介 UISheetPresentationControllerで下からピョコってするやつ楽になった! こちらは半Modalを簡単に実装できる物になります。 ライブラリだとこちらをよく使ってました。 以下がUISheetPresentationControllerを使ったmodal表示になります。 こちら参考に作ってます。 最初は半Modalで表示し、ボタンを押下すると普通のModalになるように作っています。 コード まずは簡単に実装したコードを貼ります。 こちら全部コピペしてもらえれば動きます。 ※iOS15からじゃないと使えないです。 表示Type(2種類) 呼び出し元 import UIKit class ViewController: UIViewController { var mainView: MainView! override func loadView() { super.loadView() self.mainView = MainView(frame: .zero) self.view = self.mainView } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.mainView.pushButton.addTarget(self, action: #selector(tapped(_:)),for: .touchUpInside) } // MARK: タッチイベント @objc func tapped(_ sender : Any) { present(ModalViewController.init(), animated: true, completion: nil) } } class MainView: UIView { let hogeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "テストで表示" label.font = .boldSystemFont(ofSize: 32) label.textColor = .black label.textAlignment = .center return label }() let pushButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("「ぴょこ」っと出す", for: .normal) button.setTitleColor(.white, for: .normal) button.backgroundColor = .red button.layer.cornerRadius = 25 return button }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .systemGray setComponent() setConstraint() } private func setComponent(){ [hogeLabel, pushButton].forEach{ addSubview($0) } } private func setConstraint(){ NSLayoutConstraint.activate([ hogeLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50), hogeLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), hogeLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), pushButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -20), pushButton.heightAnchor.constraint(equalToConstant: 50), pushButton.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), pushButton.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 呼び出し側 import UIKit class ModalViewController: UIViewController, UISheetPresentationControllerDelegate, ModalViewDelegate { var mainView: ModalView! var sheetPresentationController: UISheetPresentationController! override func loadView() { super.loadView() self.mainView = ModalView(frame: .zero) self.mainView.delegate = self self.view = self.mainView sheetPresentationController = presentationController as? UISheetPresentationController // modalのサイズ sheetPresentationController.delegate = self sheetPresentationController.selectedDetentIdentifier = .medium // 上部にグラバーを表示する sheetPresentationController.prefersGrabberVisible = true // 高さの定義 sheetPresentationController.detents = [ .medium(), .large() ] // 角丸数値 let cornerRadius = 80 // modalの角丸の数値反映 self.sheetPresentationController.setValue(cornerRadius, forKey: "preferredCornerRadius") } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } // MARK: ModalViewDelegate func onTapExpansionButton() { self.sheetPresentationController.animateChanges { if self.sheetPresentationController.selectedDetentIdentifier == .medium { self.sheetPresentationController.selectedDetentIdentifier = .large } else { self.sheetPresentationController.selectedDetentIdentifier = .medium } } } } protocol ModalViewDelegate: AnyObject { func onTapExpansionButton() } class ModalView: UIView { weak var delegate: ModalViewDelegate? let hugaLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "テストでModalを表示する" label.textColor = .black label.textAlignment = .center return label }() let expansion: UIButton = { let button = UIButton() button.setTitle("拡大・縮小", for: .normal) button.translatesAutoresizingMaskIntoConstraints = false button.setTitleColor(.white, for: .normal) button.backgroundColor = .black button.layer.cornerRadius = 16 return button }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .yellow setComponent() setConstraint() } // MARK: タッチイベント @objc func onTapExpansionButton(_ sender : Any) { delegate?.onTapExpansionButton() } private func setComponent(){ [hugaLabel, expansion].forEach{ addSubview($0) } self.expansion.addTarget(self, action: #selector(onTapExpansionButton(_:)),for: .touchUpInside) } private func setConstraint(){ NSLayoutConstraint.activate([ hugaLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50), hugaLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), hugaLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), expansion.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -50), expansion.heightAnchor.constraint(equalToConstant: 32), expansion.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), expansion.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 説明 重要なのはここ! コメントに書いた内容となっております。 Modalの角丸の数値は自由に変更して遊んでみてください。 // modalのサイズ sheetPresentationController.delegate = self sheetPresentationController.selectedDetentIdentifier = .medium // 上部にグラバーを表示する sheetPresentationController.prefersGrabberVisible = true // 高さの定義 sheetPresentationController.detents = [ .medium(), .large() ] // 角丸数値 let cornerRadius = 80 // modalの角丸の数値反映 self.sheetPresentationController.setValue(cornerRadius, forKey: "preferredCornerRadius") あとはボタンを押下した時の処理 sheetPresentationController.selectedDetentIdentifierで自分の設定した高さを持っています。 その為、こちらを変更すればあらびっくり変わります。 sheetPresentationController.animateChangesはアニメーション処理です func onTapExpansionButton() { self.sheetPresentationController.animateChanges { if self.sheetPresentationController.selectedDetentIdentifier == .medium { self.sheetPresentationController.selectedDetentIdentifier = .large } else { self.sheetPresentationController.selectedDetentIdentifier = .medium } } } UIButtonで色々できるようになった こちら参考 こちらも参考 UIButtonで複数行がサポートされるようになった! これまではUIButtonで複数行のテキストを表示するとなると アトリビュート を使っていたと思います。 しかし今回から楽になっちゃいました✌️ また、角丸などもcornerRadiusで定義せずとも自動でできます。 コード 複数行に対応したボタン let pushButton: UIButton = { // configの定義 var config = UIButton.Configuration.tinted() // 画像の設定 // config.image = UIImage(systemName: "person.2.circle.fill") // 画像どっち側に表示するか (何種類かあります。) // config.imagePlacement = .top config.buttonSize = .large // 角丸を自動で出す (何種類かあります。) config.cornerStyle = .capsule // 背景の設定 config.baseBackgroundColor = .systemGreen let button = UIButton(configuration: config) button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("「ぴょこ」っと出す\nひょっこりはん!!", for: .normal) button.setTitleColor(.white, for: .normal) return button }() 上記のコードを適当な画面に入れることで下記のように表示されると思います。 ちなみに画像などの設定をすると以下のように表示されます? UIButtonでインジケータの表示ができるようになった これまではライブラリなどを入れてログインボタンを押下したらAPIが走ってその際はクルクルさせるみたいなのをやっていましたが、Configでできるようになりました。 実演したのが以下になります。3秒でインジケータが終わるように書いています。 コード こちらshowsActivityIndicatorの ture or false で楽に制御できます。 画像だけクルクル回るのは気にくはない感じがある(ボソッ) // インジケーターの表示 self.mainView.pushButton.configuration?.showsActivityIndicator = true UIMenuにサブビューが追加された 多分UIMenuってIOS13からいますよね? 割と利用シーンがわからず放置していました。ActionSheet使うわってなっていましたが、 サブビュー追加は割と私の中では使ってみたいなと思いました。 ではいきましょう。 元々は2階層のものは以下のような表示でした(iOS14.5) しかしiOS15から以下になります。 階層があるものは一発でわかるようになります UIが進化して使いやすくなりました。? コード 適当に作りました。 iOS15になったからと言ってこの階層の設定については..... 何もやることがありません もう一回言います。勝手にOSがやってくれるます。その為 何もやることはありません import UIKit // メニュー表示項目 enum MenuType: String { case A = "アニメ" case B = "食べ物" case C = "電話" case D = "スターウォーズ" case E = "ゴーストバスター" } class TestMenuViewController1: UIViewController { var mainView: TestMenuView1! var selectedMenuType: MenuType = .A override func loadView() { super.loadView() self.mainView = TestMenuView1(frame: .zero) self.view = self.mainView } override func viewDidLoad() { super.viewDidLoad() // UIMenuを設定 self.menuSet() } private func menuSet() { var actions = [UIMenuElement]() [MenuType.A, MenuType.B, MenuType.C].forEach{ element in let action = UIAction( title: element.rawValue, state: self.selectedMenuType == element ? .on : .off, handler: { _ in self.selectedMenuType = element // stateの更新 self.menuSet() }) actions.append(action) } var subMenus = [UIAction]() [MenuType.D, MenuType.E].forEach{ element in let action = UIAction( title: element.rawValue, state: self.selectedMenuType == element ? .on : .off, handler: { _ in self.selectedMenuType = element // stateの更新 self.menuSet() }) subMenus.append(action) } let subMenu = UIMenu(title: "映画", children: subMenus) actions.append(subMenu) // Buttonに対してUIMenuの初期設定 self.mainView.menuButton.menu = UIMenu(title: "", options: .displayInline, children: actions) self.mainView.menuButton.showsMenuAsPrimaryAction = true self.mainView.menuButton.setTitle( self.selectedMenuType.rawValue, for: .normal ) } } class TestMenuView1: UIView { let hogeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "1個目のViewだよ!" label.font = .boldSystemFont(ofSize: 32) label.textColor = .black label.textAlignment = .center return label }() let menuButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("UIMenuを出す", for: .normal) button.setTitleColor(.white, for: .normal) button.backgroundColor = .red button.layer.cornerRadius = 25 return button }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .systemGray setComponent() setConstraint() } private func setComponent(){ [hogeLabel, menuButton].forEach{ addSubview($0) } } private func setConstraint(){ NSLayoutConstraint.activate([ hogeLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 50), hogeLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), hogeLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), menuButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -20), menuButton.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 20), menuButton.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -20), ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } シンボルが追加された! 新しく600以上シンボルが追加されました。 こちらでDLできます。 そのほか 他にはTabが透明にできるようになった(まだ確認中) TableViewのstyleに新しいものが追加された(まだ確認中) UIColorにsystemBrownというものが追加された茶色かな? 終わりに 多分私がまとめた内容が今回の変更点が全てではないと思います。 しかし主要な部分は抑えられていると思います。 それにしてもUIButtonが大幅に進化しましたね。こちらに全て説明されているので見てみるのも良いと思います。 個人的に下からぴょこっとちっちゃいModalViewがライブラリを使わず楽にできるようになったのは良いと思いました。 iOS15のリリース日はまだ先(予想が2021年秋頃)とのことなので世の中のiOSエンジニアさんたちは余裕を持って対応できると思います。iOS13みたいに死ぬほど作業量が増えるような修正がなくてよかったです。 ここまで読んでいただきありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SwiftLint】全ルールまとめ(v0.43.1)

SwiftLintのデフォルトで有効/無効になっているルールについて調べる機会があったので、ついでにまとめてみました。例については一部しか載せてないので、もっと詳細に知りたい方は各ルールの公式リファレンスを見た方がいいです。 全部で205個ルールあります。 - Default:87 - Opt-in:118 ※2021年6月時点の最新版(v0.43.1)を対象としています。 Default デフォルトで有効になっているルール Block Based KVO Swift3.2以降の場合、keypathをもつ新しいブロックベースのKVO APIを使うべき。 (observerValue()をoverrideするのではなく、observe()を呼び出すべき) https://realm.github.io/SwiftLint/block_based_kvo.html block_based_kvo // bad class Foo: NSObject { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {} } // good let observer = foo.observe(\.value, options: [.new]) { (foo, change) in print(change.newValue) } Class Delegate Protocol Delegate protocolはweakで保持される可能性があるため、class-onlyにするべき。 https://realm.github.io/SwiftLint/class_delegate_protocol.html class_delegate_protocol // bad protocol FooDelegate {} // good protocol FooDelegate: class {} Closing Brace Spacing }の後に)が続く場合は、それらの間に空白が無いようにすべき。 https://realm.github.io/SwiftLint/closing_brace.html closing_brace // bad [].map({ } ) // good [].map({ }) Closure Parameter Position クロージャのパラメータは開き括弧({)と同じ行にあるべき。 https://realm.github.io/SwiftLint/closure_parameter_position.html closure_parameter_position // bad [1, 2].map { number in number + 1 } // good [1, 2].map { number in number + 1 } Colon Spacing コロン(:)は、型を指定するときは識別子の後ろ、Dictonaryではkeyの後ろにあるべき。 https://realm.github.io/SwiftLint/colon.html colon // bad let abc:Void let abc :Void let abc : Void let abc: [Void:Void] let abc: [Void :Void] let abc: [Void : Void] // good let abc: Void let abc: [Void: Void] Comma Spacing カンマ(,)の前にスペースがあるべきではない。カンマの後ろにはスペースがあるべき。 https://realm.github.io/SwiftLint/comma.html comma // bad func abc(a: String,b: String) { } func abc(a: String ,b: String) { } func abc(a: String , b: String) { } // good func abc(a: String, b: String) { } Comment Spacing コメントする場合、スラッシュ(//)の後ろにスペースを入れるべき。 https://realm.github.io/SwiftLint/comment_spacing.html comment_spacing // bad //hogehoge // good // hogehoge Compiler Protocol Init コンパイラプロトコルで定義されているイニシャライザは直接呼び出すべきではない。 https://realm.github.io/SwiftLint/compiler_protocol_init.html compiler_protocol_init // bad let set = Set(arrayLiteral: 1, 2) let set = Set.init(arrayLiteral: 1, 2) // good let set: Set<Int> = [1, 2] let set = Set(array) Computed Accessors Order getterとsetterの順を統一すべき。基本はgetter→setterの順。 https://realm.github.io/SwiftLint/computed_accessors_order.html computed_accessors_order // bad class Foo { var foo: Int { set { print(newValue) } get { return 20 } } } // good class Foo { var foo: Int { get { return 20 } set { print(newValue) } } } setter→getterにする場合は以下を参照。 Control Statement if, for, guard, switch, while, catchは条件式や引数を、必要以上に括弧(())で囲むべきではない。 https://realm.github.io/SwiftLint/control_statement.html control_statement // bad if (condition) { // good if condition { Custom Rules .swiftlint.ymlに記述したcustome_rulesを実行する。 https://realm.github.io/SwiftLint/custom_rules.html Cyclomatic Complexity 関数内の処理を複雑にするべきではない(もっと簡潔に書くべき)。 https://realm.github.io/SwiftLint/cyclomatic_complexity.html cyclomatic_complexity // bad func f1() { if true { if true { if false {} } } if false {} let i = 0 switch i { case 1: break case 2: break case 3: break case 4: break default: break } for _ in 1...5 { guard true else { return } } } // good func f1() { if true { for _ in 1..5 { } } if false { } } もう少し詳細に説明すると「関数内の処理は循環的複雑度を10以下にするべき」というルール。 循環的複雑度(英: Cyclomatic complexity)とは、ソフトウェア測定法の一種である。Thomas McCabe が開発したもので、プログラムの複雑度を測るのに使われる。プログラムのソースコードから、線形的に独立した経路の数を直接数える。 wiki Deployment Target @availableなどを使う場合は、Deployment Targetを満たす古いバージョンを指定すべきではない。 https://realm.github.io/SwiftLint/deployment_target.html Deployment Targetが13.0の場合 deployment_target // bad @available(iOS 6.0, *) class A {} if #available(iOS 6.0, *) {} // good @available(iOS 14.0, *) class A {} if #available(iOS 14.0, *) {} Discouraged Direct Initialization 有害な可能性のある型を直接初期化すべきではない。 https://realm.github.io/SwiftLint/discouraged_direct_init.html discouraged_direct_init // bad UIDevice() Bundle() let foo = UIDevice() let foo = Bundle() let foo = Bundle.init() // good let foo = UIDevice.current let foo = Bundle.main let foo = Bundle.init(path: "bar") Duplicate Enum Cases enumには同じケース名を含めれない。 https://realm.github.io/SwiftLint/duplicate_enum_cases.html duplicate_enum_cases // bad enum PictureImport { case add(image: UIImage) case addURL(url: URL) case add(data: Data) } // good enum PictureImport { case addImage(image: UIImage) case addData(data: Data) } Duplicate Imports インポートは一意であるべき。同じインポートをするべきではない。 https://realm.github.io/SwiftLint/duplicate_imports.html duplicate_imports // bad import Foundation import Dispatch import Foundation // good import Foundation import Dispatch Duplicated Key in Dictionary Literal Dictionaryでは同じKeyを持つべきではない。 https://realm.github.io/SwiftLint/duplicated_key_in_dictionary_literal.html duplicated_key_in_dictionary_literal // bad [ foo: "1", bar: "2", foo: "3", ] // good [ foo: "1", bar: "2" ] Dynamic Inline dynamicと@inline(__always)を同時に使うべきではない。 https://realm.github.io/SwiftLint/dynamic_inline.html dynamic_inline // bad class C { @inline(__always) dynamic func f() {} } // good class C { dynamic func f() {} } Empty Enum Arguments enumの連想値が使われていない場合は、引数を省略すべき。 https://realm.github.io/SwiftLint/empty_enum_arguments.html empty_enum_arguments // bad switch foo { case .bar(_): break } // good switch foo { case .bar: break } Empty Parameters Void ->より() ->を使うべき。 https://realm.github.io/SwiftLint/empty_parameters.html empty_parameters // bad let abc: (Void) -> Void = {} // good let abc: () -> Void = {} Empty Parentheses with Trailing Closure トレイリングクロージャを使う場合、メソッド呼び出し後に空括弧()を書くべきではない。 https://realm.github.io/SwiftLint/empty_parentheses_with_trailing_closure.html empty_parentheses_with_trailing_closure // bad [1, 2].map() { $0 + 1 } // good [1, 2].map { $0 + 1 } File Length ファイルにあまりにも多くの行があってはならない。(ファイル内の処理を別ファイルに分割するなどした方が良い) https://realm.github.io/SwiftLint/file_length.html file_length // bad print("swiftlint") print("swiftlint") print("swiftlint") ... For Where for文内にif文が1つしかない場合はwhereを使うべき。 https://realm.github.io/SwiftLint/for_where.html for_where // bad for user in users { if user.id == 1 { return true } } // good for user in users where user.id == 1 { } Force Cast 強制キャスト(as!)は使用すべきではない。 https://realm.github.io/SwiftLint/force_cast.html force_cast // bad NSNumber() as! Int // good NSNumber() as? Int Force Try 強制トライ(try!)は使用すべきではない。 https://realm.github.io/SwiftLint/force_try.html force_try // bad func a() throws {} try! a() // good func a() throws {} do { try a() } catch {} Function Body Length 関数内はあまりにも多くの行にまたがるべきではない。 https://realm.github.io/SwiftLint/function_body_length.html Function Parameter Count 関数の引数の数は少なくするべき(引数は5つ以下にするべき) https://realm.github.io/SwiftLint/function_parameter_count.html function_parameter_count // bad func f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {} // good func f(a: Int, b: Int, c: Int, d: Int, e: Int) {} Generic Type Name ジェネリック型名は英数字のみを使用し、大文字で始め、1~20文字にするべき。 https://realm.github.io/SwiftLint/generic_type_name.html generic_type_name // bad func foo<T_Foo>() {} func foo<TTTTTTTTTTTTTTTTTTTTT>() {} // good func foo<T>() {} Identifier Name 識別子名には、英数字のみを使用し、小文字で始まるか、大文字のみを使用する必要がある。 例外として、変数名が静的で不変であると宣言されている場合、変数名は大文字で始まる場合がある。 変数名は長すぎても短すぎてもいけない。 https://realm.github.io/SwiftLint/identifier_name.html identifier_name // bad let MyLet = 0 let _myLet = 0 let myExtremelyVeryVeryVeryVeryVeryVeryLongLet = 0 // good let myLet = 0 let URL: NSURL? = nil Implicit Getter read-onlyのコンピューテッドプロパティとサブスクリプトでは、getを使うべきではない。 https://realm.github.io/SwiftLint/implicit_getter.html implicit_getter // bad class Foo { var foo: Int { get { return 20 } } } // good class Foo { var foo: Int { get { return 3 } set { _abc = newValue } } } Inclusive Language 識別子は、人種、性別、または社会経済的地位に基づく人々のグループに対する差別を避ける言葉を使用する必要がある。 https://realm.github.io/SwiftLint/inclusive_language.html inclusive_language // bad let slave = "abc" enum ListType { case whitelist case blacklist } Inert Defer deferが親スコープの最後にある場合、その場で実行される。 https://realm.github.io/SwiftLint/inert_defer.html inert_defer // bad func example0() { defer { /* deferred code */ } } // good func example3() { defer { /* deferred code */ } print("other code") } Is Disjoint Set.intersection(_:).isEmptyよりSet.isDisjoint(with:)を使うべき。 https://realm.github.io/SwiftLint/is_disjoint.html is_disjoint // bad _ = Set(syntaxKinds).intersection(commentAndStringKindsSet).isEmpty // good _ = Set(syntaxKinds).isDisjoint(with: commentAndStringKindsSet) Large Tuple タプルのメンバーは多すぎてはいけない。多い場合は代わりにカスタムタイプを作成するべき。 https://realm.github.io/SwiftLint/large_tuple.html large_tuple // bad let foo: (Int, Int, Int) // good let foo: (Int, Int) Leading Whitespace ファイルの先頭に空白があってはいけない。 https://realm.github.io/SwiftLint/leading_whitespace.html leading_whitespace // bad // hoge // good // hoge Legacy CGGeometry Functions 構造体のエクステンションのプロパティとメソッドは、従来の関数より優先すべき。 https://realm.github.io/SwiftLint/legacy_cggeometry_functions.html legacy_cggeometry_functions // bad CGRectGetWidth(rect) CGRectGetHeight(rect) CGRectGetMinX(rect) CGRectGetMidX(rect) CGRectGetMaxX(rect) CGRectGetMinY(rect) CGRectGetMidY(rect) CGRectGetMaxY(rect) CGRectIsNull(rect) CGRectIsEmpty(rect) CGRectIsInfinite(rect) CGRectStandardize(rect) CGRectIntegral(rect) CGRectInset(rect, 10, 5) CGRectOffset(rect, -2, 8.3) CGRectUnion(rect1, rect2) CGRectIntersection(rect1, rect2) CGRectContainsRect(rect1, rect2) CGRectContainsPoint(rect, point) CGRectIntersectsRect(rect1, rect2) // good rect.width rect.height rect.minX rect.midX rect.maxX rect.minY rect.midY rect.maxY rect.isNull rect.isEmpty rect.isInfinite rect.standardized rect.integral rect.insetBy(dx: 5.0, dy: -7.0) rect.offsetBy(dx: 5.0, dy: -7.0) rect1.union(rect2) rect1.intersect(rect2) rect1.contains(rect2) rect.contains(point) rect1.intersects(rect2) Legacy Constant 構造スコープ定数は従来のグローバル定数よりも優先すべき。 https://realm.github.io/SwiftLint/legacy_constant.html legacy_constant // bad CGRectInfinite CGPointZero CGRectZero CGSizeZero NSZeroPoint NSZeroRect NSZeroSize CGRectNull CGFloat(M_PI) Float(M_PI) // good CGRect.infinite CGPoint.zero CGRect.zero CGSize.zero NSPoint.zero NSRect.zero NSSize.zero CGRect.null CGFloat.pi Float.pi Legacy Constructor Swiftのコンストラクタは従来のコンビニエンス関数よりも優先すべき。 https://realm.github.io/SwiftLint/legacy_constructor.html legacy_constructor // bad CGPointMake(10, 10) CGPointMake(xVal, yVal) CGPointMake(calculateX(), 10) CGSizeMake(10, 10) CGSizeMake(aWidth, aHeight) CGRectMake(0, 0, 10, 10) CGRectMake(xVal, yVal, width, height) CGVectorMake(10, 10) CGVectorMake(deltaX, deltaY) NSMakePoint(10, 10) NSMakePoint(xVal, yVal) NSMakeSize(10, 10) NSMakeSize(aWidth, aHeight) NSMakeRect(0, 0, 10, 10) NSMakeRect(xVal, yVal, width, height) NSMakeRange(10, 1) NSMakeRange(loc, len) UIEdgeInsetsMake(0, 0, 10, 10) UIEdgeInsetsMake(top, left, bottom, right) NSEdgeInsetsMake(0, 0, 10, 10) NSEdgeInsetsMake(top, left, bottom, right) CGVectorMake(10, 10) NSMakeRange(10, 1) UIOffsetMake(0, 10) UIOffsetMake(horizontal, vertical) // good CGPoint(x: 10, y: 10) CGPoint(x: xValue, y: yValue) CGSize(width: 10, height: 10) CGSize(width: aWidth, height: aHeight) CGRect(x: 0, y: 0, width: 10, height: 10) CGRect(x: xVal, y: yVal, width: aWidth, height: aHeight) CGVector(dx: 10, dy: 10) CGVector(dx: deltaX, dy: deltaY) NSPoint(x: 10, y: 10) NSPoint(x: xValue, y: yValue) NSSize(width: 10, height: 10) NSSize(width: aWidth, height: aHeight) NSRect(x: 0, y: 0, width: 10, height: 10) NSRect(x: xVal, y: yVal, width: aWidth, height: aHeight) NSRange(location: 10, length: 1) NSRange(location: loc, length: len) UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 10) UIEdgeInsets(top: aTop, left: aLeft, bottom: aBottom, right: aRight) NSEdgeInsets(top: 0, left: 0, bottom: 10, right: 10) NSEdgeInsets(top: aTop, left: aLeft, bottom: aBottom, right: aRight) UIOffset(horizontal: 0, vertical: 10) UIOffset(horizontal: horizontal, vertical: vertical) Legacy Hashing hasValueをoverrideするのではなく、hash(into:)関数を使うべき。 https://realm.github.io/SwiftLint/legacy_hashing.html legacy_hashing // bad struct Foo: Hashable { let bar: Int = 10 public var hashValue: Int { return bar } } // good struct Foo: Hashable { let bar: Int = 10 func hash(into hasher: inout Hasher) { hasher.combine(bar) } } Legacy NSGeometry Functions 構造体のエクステンションのプロパティとメソッドは、従来の関数より優先すべき。 https://realm.github.io/SwiftLint/legacy_nsgeometry_functions.html legacy_nsgeometry_functions // bad NSWidth(rect) NSHeight(rect) NSMinX(rect) NSMidX(rect) NSMaxX(rect) NSMinY(rect) NSMidY(rect) NSMaxY(rect) NSEqualRects(rect1, rect2) NSEqualSizes(size1, size2) NSEqualPoints(point1, point2) NSEdgeInsetsEqual(insets2, insets2) NSIsEmptyRect(rect) NSIntegralRect(rect) NSInsetRect(rect, 10, 5) NSOffsetRect(rect, -2, 8.3) NSUnionRect(rect1, rect2) NSIntersectionRect(rect1, rect2) NSContainsRect(rect1, rect2) NSPointInRect(rect, point) NSIntersectsRect(rect1, rect2) // good rect.width rect.height rect.minX rect.midX rect.maxX rect.minY rect.midY rect.maxY rect.isEmpty rect.integral rect.insetBy(dx: 5.0, dy: -7.0) rect.offsetBy(dx: 5.0, dy: -7.0) rect1.union(rect2) rect1.intersect(rect2) rect1.contains(rect2) rect.contains(point) rect1.intersects(rect2) Line Length 1行にあまりにも多くの文字を含めるべきではない。(120文字以下にすべき) https://realm.github.io/SwiftLint/line_length.html Mark MARKコメントは有効な形式にするべき。 https://realm.github.io/SwiftLint/mark.html mark // bad //MARK: bad // MARK:bad //MARK:bad // MARK: bad // MARK: bad // MARK: -bad // MARK:- bad // MARK:-bad //MARK: - bad //MARK:- bad //MARK: -bad //MARK:-bad // MARK bad //MARK bad // MARK - bad //MARK : bad // MARKL: // MARKR // MARKK - /// MARK: /// MARK bad //MARK:- Top-Level bad mark //MARK:- Another bad mark struct MarkTest {} // MARK:- Bad mark extension MarkTest {} // good // MARK: good // MARK: - good // MARK: - // BOOKMARK // BOOKMARKS Multiple Closures with Trailing Closure 複数のクロージャを引数とする場合、トレイリングクロージャを使うべきではない。 https://realm.github.io/SwiftLint/multiple_closures_with_trailing_closure.html multiple_closures_with_trailing_closure // bad foo.something(param1: { $0 }) { $0 + 1 } // good foo.map { $0 + 1 } Nesting 型は最大1レベルの深さにネストするべきであり、関数は最大5レベルの深さにネストするべき。 https://realm.github.io/SwiftLint/nesting.html nesting // bad class Example_0 { class Example_1 { class Example_2 {} } } // good class Example_0 { class Example_1 {} } No Fallthrough Only fallthroughは、caseに少なくとも1つのステートメントが含まれている場合にのみ使うべき。 https://realm.github.io/SwiftLint/no_fallthrough_only.html no_fallthrough_only // bad switch myvar { case 1: fallthrough case 2: var a = 1 } // good switch myvar { case 1: var a = 1 fallthrough case 2: var a = 2 } No Space in Method Call メソッド名と括弧()の間にスペースを入れてはいけない。 https://realm.github.io/SwiftLint/no_space_in_method_call.html no_space_in_method_call // bad foo () // good foo() Notification Center Detachment NotificationCenterに登録した自分自身のobserverはクラスのdeinit内で除去するべき. https://realm.github.io/SwiftLint/notification_center_detachment.html notification_center_detachment // bad class Foo { func bar() { NotificationCenter.default.removeObserver(self) } } // good class Foo { deinit { NotificationCenter.default.removeObserver(self) } } class Foo { func bar() { NotificationCenter.default.removeObserver(otherObject) } } NSObject Prefer isEqual NSObjectのサブクラスは、==の代わりにisEqualを実装するべき。 https://realm.github.io/SwiftLint/nsobject_prefer_isequal.html nsobject_prefer_isequal // bad class AClass: NSObject { static func ==(lhs: AClass, rhs: AClass) -> Bool { return false } } // good class AClass: NSObject { override func isEqual(_ object: Any?) -> Bool { return true } } Opening Brace Spacing {は宣言と同じ行に置き、スペースを1つ入れるべき。 https://realm.github.io/SwiftLint/opening_brace.html opening_brace // bad func abc(){ } func abc() { } // good func abc() { } Operator Function Whitespace 演算子を定義する時、1つのスペースで囲むべき。 https://realm.github.io/SwiftLint/operator_whitespace.html operator_whitespace // bad func <|(lhs: Int, rhs: Int) -> Int {} // good func <| (lhs: Int, rhs: Int) -> Int {} Orphaned Doc Comment ドキュメントコメントは宣言の上部に書くべき。 https://realm.github.io/SwiftLint/orphaned_doc_comment.html orphaned_doc_comment // bad /// My great property // Not a doc string var myGreatProperty: String! // good /// My great property var myGreatProperty: String! Private over fileprivate fileprivateよりprivateを使用するべき。 https://realm.github.io/SwiftLint/private_over_fileprivate.html private_over_fileprivate // 例を見てもよくわからない。とりあえずfileprivateはあまり使わない方がいいということは確か // bad fileprivate class MyClass { fileprivate(set) var myInt = 4 } fileprivate enum MyEnum {} // good class MyClass { fileprivate(set) var myInt = 4 } Private Unit Test privateのUnit Testは暗黙的にスキップされる(privateにすべきではない) https://realm.github.io/SwiftLint/private_unit_test.html private_unit_test // bad private class FooTest: XCTestCase { func test1() {} internal func test2() {} public func test3() {} } // good class FooTest: XCTestCase { func test1() {} internal func test2() {} public func test3() {} } Protocol Property Accessors Order プロトコルでプロパティを宣言する場合、アクセサの順番をgetsetの順にするべき。 https://realm.github.io/SwiftLint/protocol_property_accessors_order.html protocol_property_accessors_order // bad protocol Foo { var bar: String { set get } } // good protocol Foo { var bar: String { get set } } protocol Foo { var bar: String { get } } protocol Foo { var bar: String { set } } Reduce Boolean reduce(true)やreduce(false)よりも.allSatisfy()や.contains()を使うべき。 https://realm.github.io/SwiftLint/reduce_boolean.html reduce_boolean // bad let allNines = nums.reduce(true) { $0.0 && $0.1 == 9 } let anyNines = nums.reduce(false) { $0.0 || $0.1 == 9 } // good let allNines = nums.allSatisfy { $0 == 9 } let anyNines = nums.contains { $0 == 9 } Redundant Discardable Let 関数の戻り値を使わず実行する場合、let _ = foo()より_ = foo()を使うべき。 https://realm.github.io/SwiftLint/redundant_discardable_let.html redundant_discardable_let // bad let _ = foo() // good _ = foo() Redundant @objc Attribute 冗長な@objc宣言は避けるべき。 https://realm.github.io/SwiftLint/redundant_objc_attribute.html redundant_objc_attribute // bad @objc @IBAction private func foo(_ sender: Any) {} // good @IBAction private func foo(_ sender: Any) {} Redundant Optional Initialization Optional型の変数をnilで初期化するのは冗長。 https://realm.github.io/SwiftLint/redundant_optional_initialization.html redundant_optional_initialization // bad var myVar: Int? = nil // good var myVar: Int? Redundant Set Access Control Rule プロパティのセッターのアクセスレベルが、変数のアクセスレベルと同じ場合、明示的に指定すべきではない。 https://realm.github.io/SwiftLint/redundant_set_access_control.html redundant_set_access_control // bad private(set) private var foo: Int // good private(set) public var foo: Int Redundant String Enum Value 文字列の列挙値は、caseと同じ場合省略できる。 https://realm.github.io/SwiftLint/redundant_string_enum_value.html redundant_string_enum_value // bad enum Numbers: String { case one = "one" case two = "two" } // good enum Numbers: String { case one case two } Redundant Void Return 関数宣言でVoidを返すのは冗長である。 https://realm.github.io/SwiftLint/redundant_void_return.html redundant_void_return // bad func foo() -> Void {} // good func foo() {} Returning Whitespace 戻り値の矢印と型は1つのスペースを開けるか、別の行で区切るべき。 https://realm.github.io/SwiftLint/return_arrow_whitespace.html return_arrow_whitespace // bad func abc()->Int {} func abc() ->[Int] {} func abc()-> (Int, Int) {} // good func abc() -> Int {} func abc() -> Int {} func abc() -> Int {} Shorthand Operator 省略系の演算子を使うべき。 https://realm.github.io/SwiftLint/shorthand_operator.html shorthand_operator // bad foo = foo - 1 foo = foo - aVariable // good foo -= 1 foo -= variable Statement Position elseとcatchは前の宣言に1つのスペースを入れ、同じ行に配置されるべき。 https://realm.github.io/SwiftLint/statement_position.html statement_position // bad }else if { } else { } catch { } catch { // good } else if { } else { } catch { Superfluous Disable Command 無効化されたルールが無効化された領域で違反を起こさなかった場合、SwiftLintのdisableコマンドは不要。 https://realm.github.io/SwiftLint/superfluous_disable_command.html Switch and Case Statement Alignment case文はそれを囲むswitch文と同じインデントにすべき。 https://realm.github.io/SwiftLint/switch_case_alignment.html switch_case_alignment // bad switch someBool { case true: print("red") case false: print("blue") } // good switch someBool { case true: print("red") case false: print("blue") } Syntactic Sugar シンタックスシュガー(糖衣構文)を使うべき。Arrayの代わりに[Int]を使うなど。 https://realm.github.io/SwiftLint/syntactic_sugar.html syntactic_sugar // bad let x: Array<String> // good let x: [Int] Todo TODOとFIXMEは解決すべき。 https://realm.github.io/SwiftLint/todo.html todo // bad // TODO: // FIXME: Trailing Comma 配列とDictionaryの末尾のカンマは付けるべきではない。 https://realm.github.io/SwiftLint/trailing_comma.html trailing_comma // bad let foo = [1, 2, 3,] // good let foo = [1, 2, 3] Trailing Newline ファイルは末尾に1つの改行を持つべき。 https://realm.github.io/SwiftLint/trailing_newline.html Trailing Semicolon 行の末尾にセミコロン;を付けるべきではない。 https://realm.github.io/SwiftLint/trailing_semicolon.html trailing_semicolon // bad let a = 0; // good let a = 0 Trailing Whitespace 行の末尾にスペースがあってはいけない。 https://realm.github.io/SwiftLint/trailing_whitespace.html Type Body Length 型内はあまりにも多くの行にまたがってはいけない。 https://realm.github.io/SwiftLint/type_body_length.html Type Name 型名は英数字のみを含み、大文字で始まり、3~40文字の長さにする必要がある。 https://realm.github.io/SwiftLint/type_name.html type_name // bad class myType {} // good class MyType {} Unneeded Break in Switch 不要なbreakの使用は避けるべき。 https://realm.github.io/SwiftLint/unneeded_break_in_switch.html unneeded_break_in_switch // bad switch foo { case .bar: something() break } // good switch foo { case .bar: break } Unused Capture List キャプチャリスト内の未使用の参照は削除すべき。 https://realm.github.io/SwiftLint/unused_capture_list.html unused_capture_list // bad [1, 2].map { [weak self] num in print(num) } // good [1, 2].map { num in print(num) } [1, 2].map { [weak self] num in self?.handle(num) } Unused Closure Parameter クロージャで使用されてないパラメータは_に置き換えるべき。 https://realm.github.io/SwiftLint/unused_closure_parameter.html unused_closure_parameter // bad [1, 2].map { number in return 3 } // good [1, 2].map { number in number + 1 } Unused Control Flow Label 未使用の制御フローラベルは削除すべき。 https://realm.github.io/SwiftLint/unused_control_flow_label.html unused_control_flow_label // bad loop: while true { break } // good loop: while true { break loop } Unused Enumerated インデックスかアイテムが使用されてない場合は、.enumerated()を削除すべき。 https://realm.github.io/SwiftLint/unused_enumerated.html unused_enumerated // bad for (_, foo) in bar.enumerated() { } // good for (index, foo) in bar.enumerated() { } Unused Optional Binding let _ =より!= nilを使用すべき。 https://realm.github.io/SwiftLint/unused_optional_binding.html unused_optional_binding // bad if let _ = a { } // good if a != nil { } Unused Setter Value セッターの値は使われるべき。 https://realm.github.io/SwiftLint/unused_setter_value.html unused_setter_value var aValue: String { get { return Persister.shared.aValue } set { // bad Persister.shared.aValue = aValue // good Persister.shared.aValue = newValue } } Valid IBInspectable @IBInspectableはサポートされている型の変数のみに適用されるべきであり、その型を明示的にする必要がある。 https://realm.github.io/SwiftLint/valid_ibinspectable.html valid_ibinspectable // bad class Foo { @IBInspectable private let x: Int } // good class Foo { @IBInspectable private var x: Int } Vertical Parameter Alignment 関数の定義時、パラメータが複数行にまたがっている場合は垂直方向に揃えるべき。 https://realm.github.io/SwiftLint/vertical_parameter_alignment.html vertical_parameter_alignment // bad func validateFunction(_ file: SwiftLintFile, kind: SwiftDeclarationKind, dictionary: SourceKittenDictionary) { } // good func validateFunction(_ file: SwiftLintFile, kind: SwiftDeclarationKind, dictionary: SourceKittenDictionary) { } Vertical Whitespace 空白行は1行に抑えるべき。 https://realm.github.io/SwiftLint/vertical_whitespace.html Void Return -> ()より-> Voidを使うべき。 https://realm.github.io/SwiftLint/void_return.html void_return // bad let abc: () -> () = { } // good let abc: () -> Void = { } Weak Delegate Delegateは循環参照を避けるために弱参照とすべき。 https://realm.github.io/SwiftLint/weak_delegate.html weak_delegate // bad var delegate: SomeProtocol? // good weak var delegate: SomeProtocol? XCTFail Message XCTFailの呼び出しには、アサーションの説明を含めるべき。 https://realm.github.io/SwiftLint/xctfail_message.html xctfail_message // bad func testFoo() { XCTFail() } // good func testFoo() { XCTFail("bar") } Opt-in デフォルトで無効になっているルール AnyObject Protocol class-onlyなプロトコルにはclassよりAnyObjectを使うべき。 https://realm.github.io/SwiftLint/anyobject_protocol.html anyobject_protocol // bad protocol SomeClassOnlyProtocol: class {} protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {} // good protocol SomeProtocol {} protocol SomeClassOnlyProtocol: AnyObject {} Array Init シーケンスを配列に変換する場合、seq.map { $0 }よりArray(seq)を使うべき。 https://realm.github.io/SwiftLint/array_init.html array_init // bad foo.map { $0 } // good Array(foo) Attributes 属性は、関数と型では別の行にあるべきだが、変数とimportの場合は同じ行にあるべき。 https://realm.github.io/SwiftLint/attributes.html attributes // bad @available(iOS 9.0, *) func animate(view: UIStackView) @available(iOS 9.0, *) class UIStackView @objc var x: String @testable import SourceKittenFramework // good @available(iOS 9.0, *) func animate(view: UIStackView) @available(iOS 9.0, *) class UIStackView @objc var x: String @testable import SourceKittenFramework Balanced XCTest life-cycle テストクラスはsetUp()とtearDown()をバランスよく実装すべき。 https://realm.github.io/SwiftLint/balanced_xctest_lifecycle.html balanced_xctest_lifecycle // bad final class FooTests: XCTestCase { override func setUp() {} } final class FooTests: XCTestCase { override func setUpWithError() throws {} } // good final class FooTests: XCTestCase { override func setUp() {} override func tearDown() {} } Capture Variable 作成時にクロージャーが変数をキャプチャーする混乱を避けるために、非定数変数はクロージャーのキャプチャーリストにリストすべきではない。 https://realm.github.io/SwiftLint/capture_variable.html capture_variable // bad var j: Int = 0 let closure: () -> Void = { [j] in print(j) } closure() j = 1 closure() // good var j: Int! j = 0 let closure: () -> Void = { [j] in print(j) } closure() j = 1 closure() Closure Body Length クロージャ内はあまりにも多くの行にまたがってはいけない。 https://realm.github.io/SwiftLint/closure_body_length.html closure_body_length // bad foo.bar { toto in let a0 = 0 let a0 = 0 let a0 = 0 ... } // good foo.bar { toto in let a0 = 0 } Closure End Indentation クロージャの終了は、開始した行と同じインデントにするべき。 https://realm.github.io/SwiftLint/closure_end_indentation.html closure_end_indentation // bad SignalProducer(values: [1, 2, 3]) .startWithNext { number in print(number) } // good SignalProducer(values: [1, 2, 3]) .startWithNext { number in print(number) } Closure Spacing クロージャ内は各括弧の内側に1つのスペースがあるべき。 https://realm.github.io/SwiftLint/closure_spacing.html closure_spacing // bad [].filter {$0.contains(location)} // good [].filter { $0.contains(location) } Collection Element Alignment コレクション内の全要素は垂直に揃うべき。 https://realm.github.io/SwiftLint/collection_alignment.html collection_alignment // bad let abc = [ "alpha": "a", "beta": "b", "gamma": "g", "delta": "d", "epsilon": "e" ] // good let abc = [ "alpha": "a", "beta": "b", "gamma": "g", "delta": "d", "epsilon": "e" ] Conditional Returns on Newline guardやifでreturnする際は次の行で行うべき。 https://realm.github.io/SwiftLint/conditional_returns_on_newline.html conditional_returns_on_newline // bad guard true else { return } // good guard true else { return } Contains Over Filter Count filter(where:).countを0と比較するよりcontainsを使うべき。 https://realm.github.io/SwiftLint/contains_over_filter_count.html contains_over_filter_count // bad let result = myList.filter(where: { $0 % 2 == 0 }).count > 0 let result = myList.filter(where: { $0 % 2 == 0 }).count == 0 // good let result = myList.contains { $0 % 2 == 0 } let result = myList.filter { $0 % 2 == 0 }.count > 1 Contains Over Filter Is Empty filter(where:).isEmptyを使うよりcontainsを使うべき。 https://realm.github.io/SwiftLint/contains_over_filter_is_empty.html contains_over_filter_is_empty // bad let result = myList.filter(where: { $0 % 2 == 0 }).isEmpty let result = myList.filter { $0 % 2 == 0 }.isEmpty // good let result = myList.contains(where: { $0 % 2 == 0 }) let result = myList.filter(where: { $0 % 2 == 0 }).count > 1 Contains over first not nil first(where:) != nilやfirstIndex(where:) != nilよりもcontainsを使うべき。 https://realm.github.io/SwiftLint/contains_over_first_not_nil.html contains_over_first_not_nil // bad myList.first { $0 % 2 == 0 } != nil myList.firstIndex { $0 % 2 == 0 } != nil // good myList.contains { $0 % 2 == 0 } myList.contains(where: { $0 % 2 == 0 }) Contains over range(of:) comparison to nil range(of:) != nilやrange(of:) == nilよりもcontainsを使うべき。 https://realm.github.io/SwiftLint/contains_over_range_nil_comparison.html contains_over_range_nil_comparison // bad myString.range(of: "Test") != nil myString.range(of: "Test") == nil // good myString.contains("Test") !myString.contains("Test") Convenience Type staticメンバーのみをホストするために使われる型は、インスタンス化を避けるために、caseのない列挙型として実装すべき。 https://realm.github.io/SwiftLint/convenience_type.html convenience_type // bad struct Math { public static let pi = 3.14 } class Math { public static let pi = 3.14 } // good enum Math { public static let pi = 3.14 } // 継承を伴う場合はOK class MathViewController: UIViewController { public static let pi = 3.14 } // Obj-Cに見えるクラスはOK @objc class Math: NSObject { public static let pi = 3.14 } // 静的でない型もある場合はOK struct Math { public static let pi = 3.14 public let randomNumber = 2 } Discarded Notification Center Observer ブロックを使用して通知を登録する場合、返されるオブザーバを保持し、後で削除可能な状態にするべきである。 (オブザーバを定数に入れたり、同じオブザーバを受け取れるようにメソッド化するなど、再度取得可能な状態にするべき) https://realm.github.io/SwiftLint/discarded_notification_center_observer.html discarded_notification_center_observer // bad nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil) { } // good let foo = nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil) { } func foo() -> Any { return nc.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil, using: { }) } Discouraged Assert assert(false)を使うより、assertionFailure()またはpreconditionFailure()を使用するべき。 https://realm.github.io/SwiftLint/discouraged_assert.html discouraged_assert // bad assert(false) assert(false, "foobar") // good preconditionFailure("foobar") assertionFailure("foobar") assert(true) Discouraged None Name Optional<T>.noneと競合の可能性のあるcase名やstaticメンバー名は推奨しない。 https://realm.github.io/SwiftLint/discouraged_none_name.html discouraged_none_name // bad enum MyEnum { case none } class MyClass { static let none = MyClass() } // good // 以下はルール的にはセーフだが、noneに似ているので避けた方がいいと思う enum MyEnum { case nOne } class MyClass { class var nonenone: MyClass { MyClass() } } Discouraged Object Literal オブジェクトリテラルよりイニシャライザを使うべき。 https://realm.github.io/SwiftLint/discouraged_object_literal.html discouraged_object_literal // bad let image = #imageLiteral(resourceName: "image.jpg") let color = #colorLiteral(red: value, green: value, blue: value, alpha: 1) // good let image = UIImage(named: "image") let color = UIColor(red: value, green: value, blue: value, alpha: 1) Discouraged Optional Boolean オプショナルのBool型は使うべきではない。 https://realm.github.io/SwiftLint/discouraged_optional_boolean.html discouraged_optional_boolean // bad var foo: Bool? let foo: Bool? = nil // good var foo: Bool let foo: Bool = true Discouraged Optional Collection オプショナルのコレクションを使うより空のコレクションを使うべき。 https://realm.github.io/SwiftLint/discouraged_optional_collection.html discouraged_optional_collection // bad var foo: [Int]? let foo: [Int]? = nil // good var foo: [Int] let foo: [Int] = [] Empty Collection Literal コレクションを空の配列やDictionaryと比較するより、isEmptyを使うべき。 https://realm.github.io/SwiftLint/empty_collection_literal.html empty_collection_literal // bad myArray == [] myArray != [] myDict == [:] myDict != [:] // good myArray.isEmpty Empty Count countを0と比較するよりもisEmptyを使うべき。 https://realm.github.io/SwiftLint/empty_count.html empty_count // bad [Int]().count == 0 [Int]().count > 0 [Int]().count != 0 // good [Int]().isEmpty Empty String 空の文字列と比較するより isEmptyを使うべき。 https://realm.github.io/SwiftLint/empty_string.html empty_string // bad myString == "" myString != "" // good myString.isEmpty !myString.isEmpty Empty XCTest Method 空のXCTestメソッドは実装すべきではない。 https://realm.github.io/SwiftLint/empty_xctest_method.html empty_xctest_method // bad class TotoTests: XCTestCase { override func setUp() { } override func tearDown() { } func testFoo() { } } // good class TotoTests: XCTestCase { var foobar: Foobar? override func setUp() { super.setUp() foobar = Foobar() } override func tearDown() { foobar = nil super.tearDown() } func testFoo() { XCTAssertTrue(foobar?.foo) } func helperFunction() { } } Enum Case Associated Values Count 列挙型のcaseの連想値は少なくするべき(4つ以下にするべき) https://realm.github.io/SwiftLint/enum_case_associated_values_count.html enum_case_associated_values_count // bad enum Employee { case fullTime(name: String, retirement: Date, age: Int, designation: String, contactNumber: Int) case partTime(name: String, contractEndDate: Date, age: Int, designation: String, contactNumber: Int) } enum Barcode { case upc(Int, Int, Int, Int, Int, Int) } // good enum Employee { case fullTime(name: String, retirement: Date, designation: String, contactNumber: Int) case partTime(name: String, age: Int, contractEndDate: Date) } enum Barcode { case upc(Int, Int, Int, Int) } ExpiringTodo TODOとFIXMEは有効期限前に解決すべき。 https://realm.github.io/SwiftLint/expiring_todo.html expiring_todo // bad // TODO: [1/1/2019] // FIXME: [1/1/2019] // good // TODO: [12/31/9999] // FIXME: [12/31/9999] Explicit ACL 全ての宣言で、アクセス制御レベルのキーワードを明示的に指定すべき。 https://realm.github.io/SwiftLint/explicit_acl.html explicit_acl // bad enum A {} final class B {} internal struct C { let d = 5 } public struct C { let d = 5 } func a() {} // good internal enum A {} public final class B {} private struct C {} internal enum A { internal enum B {} } internal final class Foo {} Explicit Enum Raw Value 列挙型のcaseにはローバリューを明示的に割り当てるべき。 https://realm.github.io/SwiftLint/explicit_enum_raw_value.html explicit_enum_raw_value // bad enum Numbers: Int { case one case two } // good enum Numbers: Int { case one = 1 case two = 2 } Explicit Init .init()を明示的に呼び出すのは避けるべき。 https://realm.github.io/SwiftLint/explicit_init.html explicit_init // bad [1].flatMap{String.init($0)} [String.self].map { Type in Type.init(1) } func foo() -> [String] { return [1].flatMap { String.init($0) } } // good [1].flatMap(String.init) [String.self].map { $0.init(1) } [String.self].map { type in type.init(1) } Explicit Self インスタンス変数と関数はself.で明示的にアクセスするべき。 https://realm.github.io/SwiftLint/explicit_self.html explicit_self // bad struct A { func f1() {} func f2() { f1() } } struct A { let p1: Int func f1() { _ = p1 } } // good struct A { func f1() {} func f2() { self.f1() } } struct A { let p1: Int func f1() { _ = self.p1 } } Explicit Top Level ACL 最上位の定義は、アクセス制御レベルのキーワードを明示的に指定するべき。 https://realm.github.io/SwiftLint/explicit_top_level_acl.html explicit_top_level_acl // bad enum A { enum B {} } // good internal enum A { enum B {} } Explicit Type Interface プロパティには型を明示的に指定すべき。 https://realm.github.io/SwiftLint/explicit_type_interface.html explicit_type_interface // bad class Foo { var myVar = 0 } // good class Foo { var myVar: Int? = 0 } Extension Access Modifier extensionにはアクセス修飾子を使うべき。 https://realm.github.io/SwiftLint/extension_access_modifier.html extension_access_modifier // bad extension Foo { var bar: Int { return 1 } var baz: Int { return 1 } } // good public extension Foo { var bar: Int { return 1 } var baz: Int { return 1 } } extension Foo { private var bar: Int { return 1 } public var baz: Int { return 1 } } Fallthrough fallthroughは使うべきではない。 https://realm.github.io/SwiftLint/fallthrough.html fallthrough // bad switch foo { case .bar: fallthrough case .bar2: something() } // good switch foo { case .bar: hoge() case .bar2: something() } Fatal Error Message fatalErrorはメッセージを付けるべき。 https://realm.github.io/SwiftLint/fatal_error_message.html fatal_error_message // bad func foo() { fatalError("") } // good func foo() { fatalError("Foo") } File Header 各ファイルのヘッダーコメントは、一貫性のあるパターンであるべき。 (ヘッダーコメントの内容は統一すべき的なこと) https://realm.github.io/SwiftLint/file_header.html File Name ファイル名は、ファイル内で宣言されている型やextensionと一致すべき。 https://realm.github.io/SwiftLint/file_name.html File Name No Space ファイル名にはスペースが入ってはいけない。 https://realm.github.io/SwiftLint/file_name_no_space.html File Types Order ファイル内の型の表示順を指定すべき。 https://realm.github.io/SwiftLint/file_types_order.html file_types_order // bad // `protocol` (サポートする型)は `class` (主となる型)の前に書くべき class TestViewController: UIViewController {} protocol TestViewControllerDelegate { func didPressTrackedButton() } // `extension` は `class` の後に書くべき extension TestViewController: UITableViewDataSource { // (省略) } class TestViewController: UIViewController {} // good // Supporting Types protocol TestViewControllerDelegate: AnyObject { func didPressTrackedButton() } // Main Type class TestViewController: UIViewController { // Type Aliases typealias CompletionHandler = ((TestEnum) -> Void) // Subtypes class TestClass { // 10 lines } struct TestStruct { // 3 lines } enum TestEnum { // 5 lines } // Stored Type Properties static let cellIdentifier: String = "AmazingCell" // Stored Instance Properties var shouldLayoutView1: Bool! weak var delegate: TestViewControllerDelegate? private var hasLayoutedView1: Bool = false private var hasLayoutedView2: Bool = false // Computed Instance Properties private var hasAnyLayoutedView: Bool { return hasLayoutedView1 || hasLayoutedView2 } // IBOutlets @IBOutlet private var view1: UIView! @IBOutlet private var view2: UIView! // Initializers override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // Type Methods static func makeViewController() -> TestViewController { return TestViewController() } // Life-Cycle Methods override func viewDidLoad() { super.viewDidLoad() view1.setNeedsLayout() view1.layoutIfNeeded() hasLayoutedView1 = true } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view2.setNeedsLayout() view2.layoutIfNeeded() hasLayoutedView2 = true } // IBActions @IBAction func goNextButtonPressed() { goToNextVc() delegate?.didPressTrackedButton() } @objc func goToRandomVcButtonPressed() { goToRandomVc() } // MARK: Other Methods func goToNextVc() { /* TODO */ } func goToInfoVc() { /* TODO */ } func goToRandomVc() { let viewCtrl = getRandomVc() present(viewCtrl, animated: true) } private func getRandomVc() -> UIViewController { return UIViewController() } // Subscripts subscript(_ someIndexThatIsNotEvenUsed: Int) -> String { get { return "This is just a test" } set { print("Just a test \(newValue)") } } } // Extensions extension TestViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() } } First Where コレクションでは.filter { }.firstより.first(where:)を使うべき。 https://realm.github.io/SwiftLint/first_where.html first_where // bad myList.filter { $0 % 2 == 0 }.first // good myList.first(where: { $0 % 2 == 0 }) myList.first { $0 % 2 == 0 } FlatMap over map and reduce reduce([], +)が後に続くmapよりもflatMapを使うべき。 https://realm.github.io/SwiftLint/flatmap_over_map_reduce.html flatmap_over_map_reduce // bad let foo = bar.map { $0.array }.reduce([], +) // good let foo = bar.flatMap { $0.array } Force Unwrapping 強制アンラップは避けるべき。 https://realm.github.io/SwiftLint/force_unwrapping.html force_unwrapping // bad let url = NSURL(string: query)! // good if let url = NSURL(string: query) Function Default Parameter at End 関数でデフォルト値を持つ引数は後ろにまとめるべき。 https://realm.github.io/SwiftLint/function_default_parameter_at_end.html function_default_parameter_at_end // bad func foo(y: Int = 0, x: String, z: CGFloat = 0) { } // good func foo(x: String, y: Int = 0, z: CGFloat = 0) { } IBInspectable in Extension extensionには@IBInspectableプロパティを追加すべきではない。 https://realm.github.io/SwiftLint/ibinspectable_in_extension.html ibinspectable_in_extension // bad extension Foo { @IBInspectable private var x: Int } // good class Foo { @IBInspectable private var x: Int } Identical Operands 同一のオペランドを比較するのは間違っている可能性がある。 https://realm.github.io/SwiftLint/identical_operands.html identical_operands // bad 1 == 1 foo == foo // good 1 == 2 foo == bar Implicit Return クロージャ、関数、ゲッターでは暗黙的なリターンを使うべき。 https://realm.github.io/SwiftLint/implicit_return.html implicit_return // bad foo.map { return $0 + 1 } func foo() -> Int { return 0 } class Foo { var bar: Int { get { return 0 } } } // good foo.map { $0 + 1 } func foo() -> Int { 0 } class Foo { var bar: Int { get { 0 } } } Implicitly Unwrapped Optional 暗黙的にアンラップされているオプショナル型は可能な限り使うべきではない。 https://realm.github.io/SwiftLint/implicitly_unwrapped_optional.html implicitly_unwrapped_optional // bad var int: Int! = 42 let label: UILabel! // good let int: Int? = 42 @IBOutlet var label: UILabel! Indentation Width 1つのタブまたは設定したスペースの量でコードをインデントすべき。 https://realm.github.io/SwiftLint/indentation_width.html indentation_width // bad firstLine secondLine // good firstLine secondLine Joined Default Parameter デフォルトの区切り文字は明示的に使うべきではない。 https://realm.github.io/SwiftLint/joined_default_parameter.html joined_default_parameter // bad let foo = bar.joined(separator: "") // good let foo = bar.joined() let foo = bar.joined(separator: ",") Last Where コレクションでは.filter { }.lastより.last(where:)を使うべき。 https://realm.github.io/SwiftLint/last_where.html last_where // bad myList.filter { $0 % 2 == 0 }.last // good myList.last(where: { $0 % 2 == 0 }) Legacy Multiple 剰余演算子%を使うよりisMultiple(of:)を使うべき。 https://realm.github.io/SwiftLint/legacy_multiple.html legacy_multiple // bad let constant = 56 let isMultiple = value % constant == 0 // good let constant = 56 let isMultiple = value.isMultiple(of: constant) Legacy Objective-C Reference Type bridgeされたObjective-Cの参照型よりSwiftの値型を使うべき。 https://realm.github.io/SwiftLint/legacy_objc_type.html legacy_objc_type // bad var array = NSArray() var calendar: NSCalendar? = nil // good var array = Array<Int>() var calendar: Calendar? = nil Legacy Random 従来の関数よりtype.random(in:)を使うべき。 https://realm.github.io/SwiftLint/legacy_random.html legacy_random // bad arc4random(10) arc4random_uniform(83) drand48(52) // good Int.random(in: 0..<10) Double.random(in: 8.6...111.34) Float.random(in: 0..<1) Variable Declaration Whitespace letとvarは他のステートメントと空白行で区切るべき。 https://realm.github.io/SwiftLint/let_var_whitespace.html let_var_whitespace // bad let a = 0 var x = 1 x = 2 // good let a = 0 var x = 1 x = 2 Literal Expression End Indentation 配列とdictionaryのリテラルの末尾は、開始した行と同じインデントにするべき。 https://realm.github.io/SwiftLint/literal_expression_end_indentation.html literal_expression_end_indentation // bad let x = [ 1, 2 ] let x = [ 1, 2 ] let x = [ key: value ] // good [1, 2, 3] [1, 2 ] [ 1, 2 ] let x = [ 1, 2 ] Lower ACL than parent 定義のアクセス制御レベルは、親と同じか低くするべき。 https://realm.github.io/SwiftLint/lower_acl_than_parent.html lower_acl_than_parent // bad struct Foo { public func bar() {} } // good public struct Foo { public func bar() {} } Missing Docs 宣言はドキュメント化するべき。 https://realm.github.io/SwiftLint/missing_docs.html missing_docs // good /// docs public class A { /// docs public func b() {} } /// docs public class B: A { override public func b() {} } /// docs public class B: NSObject { // オーバーライドメソッドはOK override public var description: String { fatalError() } } /// docs public class A { deinit {} } } Modifier Order 修飾子の順序は一貫しているべき。 https://realm.github.io/SwiftLint/modifier_order.html modifier_order // bad public class Foo { static public let bar = 42 } // good public class Foo { public static let bar = 42 } Multiline Arguments 引数は同じ行に置くか、1行に1つずつ指定すべき。 https://realm.github.io/SwiftLint/multiline_arguments.html multiline_arguments // bad foo(0, param1: 1, param2: true, param3: [3]) foo(0, param1: 1, param2: true, param3: [3]) // good foo(0) foo(param1: 1, param2: true) { } foo(param1: 1, param2: true, param3: [3]) Multiline Arguments Brackets 複数行の引数には、新しい行に括弧をつけるべき。 https://realm.github.io/SwiftLint/multiline_arguments_brackets.html multiline_arguments_brackets // bad foo(param1: "Param1", param2: "Param2", param3: "Param3" ) foo( param1: "Param1", param2: "Param2", param3: "Param3") foo(bar( x: 5, y: 7 ) ) // good foo(param1: "Param1", param2: "Param2", param3: "Param3") foo( param1: "Param1", param2: "Param2", param3: "Param3" ) func foo( param1: "Param1", param2: "Param2", param3: "Param3" ) Multiline Function Chains 関数を連鎖的に呼び出す場合、同じ行で呼び出すか、1行に1つずつ呼び出すべき。 https://realm.github.io/SwiftLint/multiline_function_chains.html multiline_function_chains // bad let evenSquaresSum = [20, 17, 35, 4] .filter { $0 % 2 == 0 }.map { $0 * $0 } .reduce(0, +) let evenSquaresSum = a.b(1, 2, 3) .c { blah in print(blah) }.d() // good let evenSquaresSum = [20, 17, 35, 4].filter { $0 % 2 == 0 }.map { $0 * $0 }.reduce(0, +) let evenSquaresSum = [20, 17, 35, 4] .filter { $0 % 2 == 0 }.map { $0 * $0 }.reduce(0, +)", let chain = a .b(1, 2, 3) .c { blah in print(blah) } .d() Multiline Literal Brackets 複数行のリテラルの場合、新しい行に括弧をつけるべき。 https://realm.github.io/SwiftLint/multiline_literal_brackets.html multiline_literal_brackets // bad let trio = ["harry", "ronald", "hermione" ] let houseCup = ["gryffinder": 460, "hufflepuff": 370, "ravenclaw": 410, "slytherin": 450 ] // good let trio = ["harry", "ronald", "hermione"] let trio = [ "harry", "ronald", "hermione" ] Multiline Parameters 関数とメソッドの引数は、同じ行におくか、1行に1つずつ指定するべき。 https://realm.github.io/SwiftLint/multiline_parameters.html multiline_parameters // bad func foo(param1: Int, param2: Bool, param3: [String]) { } // good func foo(param1: Int, param2: Bool) { } func foo(param1: Int, param2: Bool, param3: [String]) { } Multiline Parameters Brackets 複数行の引数は、新しい行に括弧をつけるべき。 https://realm.github.io/SwiftLint/multiline_parameters_brackets.html multiline_parameters_brackets // bad func foo(param1: String, param2: String, param3: String ) func foo( param1: String, param2: String, param3: String) // good func foo(param1: String, param2: String, param3: String) func foo( param1: String, param2: String, param3: String ) func foo( param1: String, param2: String, param3: String ) Nimble Operator フリーのmatcher関数より、Nimble演算子のオーバーロードを使うべき。 https://realm.github.io/SwiftLint/nimble_operator.html nimble_operator // bad expect(seagull.squawk).to(equal("Hi")) expect(seagull.squawk).toNot(equal("Hi")) expect(10).to(beGreaterThan(2)) // good expect(seagull.squawk) == "Hi!" expect(seagull.squawk) != "Hi!" expect(10) > 2 No Extension Access Modifier extensionにアクセス修飾子をつけるべきではない。 https://realm.github.io/SwiftLint/no_extension_access_modifier.html no_extension_access_modifier // bad private extension String {} // good extension String {} No Grouping Extension 同じソースファイル内のコードをグループ化するためにextensionを使用すべきではない。 https://realm.github.io/SwiftLint/no_grouping_extension.html no_grouping_extension // bad enum Fruit {} extension Fruit {} struct Tea {} extension Tea: Error {} // good // プロトコルのデフォルト実装はOK protocol Food {} extension Food {} NSLocalizedString Key genstringsが機能するためには、NSLocalizedStringのkeyとして静的文字列を使うべき。 https://realm.github.io/SwiftLint/nslocalizedstring_key.html nslocalizedstring_key // bad NSLocalizedString(method(), comment: "") NSLocalizedString("key_\(param)", comment: "") // good NSLocalizedString("key", comment: "") NSLocalizedString("key" + "2", comment: "") NSLocalizedString Require Bundle NSLocalizedStringの呼び出しは、文字列ファイルを含むバンドルを指定する必要がある。 https://realm.github.io/SwiftLint/nslocalizedstring_require_bundle.html nslocalizedstring_require_bundle // bad NSLocalizedString("someKey", comment: "test") NSLocalizedString("someKey", tableName: "a", comment: "test") NSLocalizedString("someKey", tableName: "xyz", value: "test", comment: "test") // good NSLocalizedString("someKey", bundle: .main, comment: "test") NSLocalizedString("someKey", tableName: "a", bundle: Bundle(for: A.self), comment: "test") NSLocalizedString("someKey", tableName: "xyz", bundle: someBundle, value: "test" comment: "test") Number Separator _は十進数で千の区切り文字として使うべき。 https://realm.github.io/SwiftLint/number_separator.html number_separator // bad let foo = -10_0 let foo = -1000 // good let foo = -1_000 let foo = -1_000_000 let foo = -1.000_1 Object Literal 画像や色はイニシャライザよりもオブジェクトリテラルを使うべき。 https://realm.github.io/SwiftLint/object_literal.html object_literal // bad let image = UIImage(named: "foo") let color = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1) // good let image = #imageLiteral(resourceName: "image.jpg") let color = #colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1) Operator Usage Whitespace 演算子を使用するときは、演算子をスペースで囲むべき。 https://realm.github.io/SwiftLint/operator_usage_whitespace.html operator_usage_whitespace // bad let foo =1+2 let foo = 1 > 2 // good let foo = 1 + 2 let foo = 1 > 2 Optional Enum Case Match ?のないオプショナルの列挙型に対する、列挙型のケースの一致は、Swift5.1以降でサポートされる。 (Swift5.1以降を使用してない場合はこの方法は使用できない。) https://realm.github.io/SwiftLint/optional_enum_case_matching.html optional_enum_case_matching // bad switch foo { case .bar?: break case .baz: break default: break } // good switch foo { case .bar: break case .baz: break default: break } Overridden methods call super 一部のオーバーライドメソッドは、常にsuperを呼び出す必要がある。 https://realm.github.io/SwiftLint/overridden_super_call.html overridden_super_call // bad class VC: UIViewController { override func viewWillAppear(_ animated: Bool) { //Not calling to super self.method() } } class VC: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) //Other code super.viewWillAppear(animated) } } class VC: UIViewController { override func didReceiveMemoryWarning() { } } // good class VC: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } class VC: UIViewController { override func viewWillAppear(_ animated: Bool) { self.method1() super.viewWillAppear(animated) self.method2() } } class VC: UIViewController { override func loadView() { } } class Some { func viewWillAppear(_ animated: Bool) { } } class VC: UIViewController { override func viewDidLoad() { defer { super.viewDidLoad() } } } Override in Extension extensionでは定義をoverrideすべきではない。 https://realm.github.io/SwiftLint/override_in_extension.html override_in_extension // bad extension Person { override var age: Int { return 42 } } extension Person { override func celebrateBirthday() {} } // good extension Person { var age: Int { return 42 } } extension Person { func celebrateBirthday() {} } Pattern Matching Keywords キーワードをタプルの外に出し、複数のパターンマッチングバインディングを組み合わせるべき。 https://realm.github.io/SwiftLint/pattern_matching_keywords.html pattern_matching_keywords // bad switch foo { case (let x, let y): break } // good switch foo { case let (x, y): break } Prefer Nimble XCTAssert関数よりNimbleマッチャーを使うべき。 https://realm.github.io/SwiftLint/prefer_nimble.html prefer_nimble // bad XCTAssertTrue(foo) XCTAssertEqual(foo, 2) // good expect(foo) == 1 expect(foo).to(equal(1)) Prefer Self Type Over Type of Self プロパティにアクセスするときや、メソッドを呼び出すときは、type(of: self)よりSelfを使うべき。 https://realm.github.io/SwiftLint/prefer_self_type_over_type_of_self.html prefer_self_type_over_type_of_self // bad class Foo { func bar() { type(of: self).baz() } } // good class Foo { func bar() { Self.baz() } } Prefer Zero Over Explicit Init パラメータが0の明示的な初期化(例:CGPoint(x: 0, y: 0))より.zeroを使うべき。 https://realm.github.io/SwiftLint/prefer_zero_over_explicit_init.html prefer_zero_over_explicit_init // bad CGPoint(x: 0, y: 0) // good CGPoint.zero Prefixed Top-Level Constant 最上位の定数には、定数名の先頭にkをつけるべき。 https://realm.github.io/SwiftLint/prefixed_toplevel_constant.html prefixed_toplevel_constant // bad private let foo = 20.0 // good private let kFoo = 20.0 Private Actions @IBActionはprivateにすべき。 https://realm.github.io/SwiftLint/private_action.html private_action // bad @IBAction func barButtonTapped(_ sender: UIButton) {} // good @IBAction private func barButtonTapped(_ sender: UIButton) {} Private Outlets @IBOutletはprivateにすべき。(上位レイヤーにUIKitが漏洩するのを防ぐため) https://realm.github.io/SwiftLint/private_outlet.html private_outlet // bad @IBOutlet var label: UILabel? // good @IBOutlet private var label: UILabel? Private Combine Subject Combine Subjectはprivateにするべき。 https://realm.github.io/SwiftLint/private_subject.html private_subject // bad final class Foobar { let goodSubject = PassthroughSubject<Bool, Never>() } // good final class Foobar { private let goodSubject = PassthroughSubject<Bool, Never>() } Prohibited Interface Builder IB(InterfaceBuilder)を使ってViewを生成するのは避けるべき。 https://realm.github.io/SwiftLint/prohibited_interface_builder.html prohibited_interface_builder // bad class ViewController: UIViewController { @IBOutlet var label: UILabel! @IBAction func buttonTapped(_ sender: UIButton) {} } // good class ViewController: UIViewController { var label: UILabel! @objc func buttonTapped(_ sender: UIButton) {} } Prohibited calls to super 一部のメソッドはsuper(親クラスのメソッド)を呼び出すべきではない。 https://realm.github.io/SwiftLint/prohibited_super_call.html prohibited_super_call // bad class VC: UIViewController { override func loadView() { super.loadView() } } Quick Discouraged Call describeやcontext内で推奨されない呼び出し。 https://realm.github.io/SwiftLint/quick_discouraged_call.html quick_discouraged_call // bad class TotoTests: QuickSpec { override func spec() { describe("foo") { let foo = Foo() } } } // good class TotoTests: QuickSpec { override func spec() { describe("foo") { beforeEach { let foo = Foo() foo.toto() } } } } Quick Discouraged Focused Test 一部のみ有効となるテストは実行すべきではない。 https://realm.github.io/SwiftLint/quick_discouraged_focused_test.html quick_discouraged_focused_test class TotoTests: QuickSpec { override func spec() { // bad fdescribe("foo") { } fcontext("foo") { } fit("foo") { } fitBehavesLike("foo") // good describe("foo") { } context("foo") { } it("foo") { } itBehavesLike("foo") } } Quick Discouraged Pending Test 一部のみ無効となるテストは実行すべきではない。 https://realm.github.io/SwiftLint/quick_discouraged_pending_test.html quick_discouraged_pending_test class TotoTests: QuickSpec { override func spec() { // bad xdescribe("foo") { } xcontext("foo") { } xit("foo") { } pending("foo") xitBehavesLike("foo") // good describe("foo") { } context("foo") { } it("foo") { } itBehavesLike("foo") } } Raw Value For Camel Cased Codable Enum CodableとStringを継承した列挙型のCamelケースの場合は値を持つべき。 https://realm.github.io/SwiftLint/raw_value_for_camel_cased_codable_enum.html raw_value_for_camel_cased_codable_enum // bad enum Status: String, Codable { case ok case notAcceptable case maybeAcceptable = "maybe_acceptable" } // good enum Status: String, Codable { case ok case notAcceptable = "not_acceptable" case maybeAcceptable = "maybe_acceptable" } Reduce Into copy-on-writeの場合、reduce(_:_:)よりreduce(into:_:)を使うべき。 https://realm.github.io/SwiftLint/reduce_into.html reduce_into // bad let bar = values.reduce("abc") { $0 + "\($1)" } // good let foo = values.reduce(into: "abc") { $0 += "\($1)" } Redundant Nil Coalescing ??演算子は、左辺がnilの場合のみ評価されるため、右辺にnilを記述するのは冗長。 https://realm.github.io/SwiftLint/redundant_nil_coalescing.html redundant_nil_coalescing // bad var myVar: Int? = nil myVar ?? nil // good var myVar: Int? myVar ?? 0 Redundant Type Annotation 変数に冗長な型アノテーションをつけてはいけない。 https://realm.github.io/SwiftLint/redundant_type_annotation.html redundant_type_annotation // bad var url:URL = URL() var isEnabled: Bool = true // good var url = URL() var url: CustomStringConvertible = URL() Required Deinit クラスにはdeinitメソッドを明示的に持つべき。 https://realm.github.io/SwiftLint/required_deinit.html required_deinit // bad class Apple { } // good class Apple { deinit { } } Required Enum Case 特定のプロトコルに準拠する列挙型は、特定のケースを実装するべき。 https://realm.github.io/SwiftLint/required_enum_case.html required_enum_case // bad enum MyNetworkResponse: String, NetworkResponsable { case success, error } // good enum MyNetworkResponse: String, NetworkResponsable { case success, error, notConnected } enum MyNetworkResponse: String, NetworkResponsable { case success, error, notConnected(error: Error) } Single Test Class テストファイルには、単一のQuickSpecまたはXCTestCaseクラスを含むべき(複数含むべきではない) https://realm.github.io/SwiftLint/single_test_class.html single_test_class // bad class FooTests: QuickSpec { } class BarTests: QuickSpec { } class FooTests: XCTestCase { } class BarTests: XCTestCase { } class FooTests: QuickSpec { } class BarTests: XCTestCase { } // good class FooTests: QuickSpec { } class FooTests: XCTestCase { } Min or Max over Sorted First or Last sorted().firstやsorted().lastよりmin()やmax()を使うべき。 https://realm.github.io/SwiftLint/sorted_first_last.html sorted_first_last // bad myList.sorted().first myList.sorted().last // good let min = myList.min() let max = myList.max() Sorted Imports import文はソートされるべき。 https://realm.github.io/SwiftLint/sorted_imports.html sorted_imports // bad import AAA import ZZZ import BBB import CCC // good import AAA import BBB import CCC import DDD Static Operator 演算子は、自由関数ではなく静的関数として定義すべき。 https://realm.github.io/SwiftLint/static_operator.html static_operator // bad func == (lhs: A, rhs: A) -> Bool { return false } func == <T>(lhs: A<T>, rhs: A<T>) -> Bool { return false } // good class A: Equatable { static func == (lhs: A, rhs: A) -> Bool { return false } } class A<T>: Equatable { static func == <T>(lhs: A<T>, rhs: A<T>) -> Bool { return false } } Strict fileprivate fileprivateは避けるべき。 https://realm.github.io/SwiftLint/strict_fileprivate.html strict_fileprivate // bad class MyClass { fileprivate let myInt = 4 } // good class MyClass { private let myInt = 4 } Strong IBOutlet @IBOutletはweakで定義すべきではない。 https://realm.github.io/SwiftLint/strong_iboutlet.html strong_iboutlet // bad class ViewController: UIViewController { @IBOutlet weak var label: UILabel? } // good class ViewController: UIViewController { @IBOutlet var label: UILabel? } Switch Case on Newline switch文内のcaseは常に改行すべき。 https://realm.github.io/SwiftLint/switch_case_on_newline.html switch_case_on_newline // bad switch foo { case 1: return true } // good switch foo { case 1: return true } Test case accessibility テストケースには、privateの非テストメンバーのみ含めるべき。 https://realm.github.io/SwiftLint/test_case_accessibility.html test_case_accessibility // bad class FooTests: XCTestCase { var foo: String? let bar: String? static func foo() {} func setUp(withParam: String) {} func foobar() {} func not_testBar() {} enum Nested {} static func testFoo() {} static func allTests() {} func testFoo(hasParam: Bool) {} } // good let foo: String? class FooTests: XCTestCase { static let allTests: [String] = [] private let foo: String { let nestedMember = "hi" return nestedMember } override static func setUp() { super.setUp() } override func setUp() { super.setUp() } override func setUpWithError() throws { try super.setUpWithError() } override static func tearDown() { super.tearDown() } override func tearDown() { super.tearDown() } override func tearDownWithError() { try super.tearDownWithError() } override func someFutureXCTestFunction() { super.someFutureXCTestFunction() } func testFoo() { XCTAssertTrue(true) } func testBar() { func nestedFunc() {} } private someFunc(hasParam: Bool) {} } Toggle Bool someBool = !someBool.よりsomeBool.toggle()を使うべき。 https://realm.github.io/SwiftLint/toggle_bool.html toggle_bool // bad isHidden = !isHidden // good isHidden.toggle() Trailing Closure 可能な限り、トレイリングクロージャを使うべき。 https://realm.github.io/SwiftLint/trailing_closure.html trailing_closure // bad foo.map({ $0 + 1 }) // good foo.map { $0 + 1 } Type Contents Order 型内のサブクラス、プロパティ、メソッドの定義順は従うべき。 https://realm.github.io/SwiftLint/type_contents_order.html type_contents_order // bad class TestViewController: UIViewController { // Subtypes class TestClass { // 10 lines } // Type Aliases typealias CompletionHandler = ((TestEnum) -> Void) } class TestViewController: UIViewController { // Stored Type Properties static let cellIdentifier: String = "AmazingCell" // Subtypes class TestClass { // 10 lines } } class TestViewController: UIViewController { // Stored Instance Properties var shouldLayoutView1: Bool! // Stored Type Properties static let cellIdentifier: String = "AmazingCell" } // good class TestViewController: UIViewController { // Type Aliases typealias CompletionHandler = ((TestEnum) -> Void) // Subtypes class TestClass { // 10 lines } struct TestStruct { // 3 lines } enum TestEnum { // 5 lines } // Type Properties static let cellIdentifier: String = "AmazingCell" // Instance Properties var shouldLayoutView1: Bool! weak var delegate: TestViewControllerDelegate? private var hasLayoutedView1: Bool = false private var hasLayoutedView2: Bool = false private var hasAnyLayoutedView: Bool { return hasLayoutedView1 || hasLayoutedView2 } // IBOutlets @IBOutlet private var view1: UIView! @IBOutlet private var view2: UIView! // Initializers override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // Type Methods static func makeViewController() -> TestViewController { // some code } // View Life-Cycle Methods override func viewDidLoad() { super.viewDidLoad() view1.setNeedsLayout() view1.layoutIfNeeded() hasLayoutedView1 = true } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view2.setNeedsLayout() view2.layoutIfNeeded() hasLayoutedView2 = true } // IBActions @IBAction func goNextButtonPressed() { goToNextVc() delegate?.didPressTrackedButton() } // Other Methods func goToNextVc() { /* TODO */ } func goToInfoVc() { /* TODO */ } func goToRandomVc() { let viewCtrl = getRandomVc() present(viewCtrl, animated: true) } private func getRandomVc() -> UIViewController { return UIViewController() } // Subscripts subscript(_ someIndexThatIsNotEvenUsed: Int) -> String { get { return "This is just a test" } set { log.warning("Just a test", newValue) } } deinit { log.debug("deinit") }, } Unavailable Function 未実装の関数は使用不可としてマークするべき。 https://realm.github.io/SwiftLint/unavailable_function.html unavailable_function // bad class ViewController: UIViewController { public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // good class ViewController: UIViewController { @available(*, unavailable) public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } Unneeded Parentheses in Closure Argument クロージャの引数を定義するとき、括弧は必要ない。 https://realm.github.io/SwiftLint/unneeded_parentheses_in_closure_argument.html unneeded_parentheses_in_closure_argument // bad let foo = { (bar) -> Bool in return true } // good let foo = { bar -> Bool in return true } Unowned Variable Capture クラッシュの可能性を回避するため、弱参照にしてキャプチャするべき。 https://realm.github.io/SwiftLint/unowned_variable_capture.html unowned_variable_capture // bad foo { [unowned self] in _ } foo { [unowned bar] in _ } foo { [bar, unowned self] in _ } // good foo { [weak self] in _ } foo { [weak self] param in _ } foo { [weak bar] in _ } foo { [weak bar] param in _ } Untyped Error in Catch catchで、型キャストなしのエラー変数を定義すべきではない。 https://realm.github.io/SwiftLint/untyped_error_in_catch.html untyped_error_in_catch // bad do { try foo() } catch var error {} // good do { try foo() } catch let error as MyError { } catch {} Unused Declaration 定義は少なくとも1回は全てのファイル内のどこかで参照されるべき。 https://realm.github.io/SwiftLint/unused_declaration.html unused_declaration // bad let foo = 0 // good let foo = 0 _ = foo Unused Import importされた全てのモジュールが、ファイルをコンパイルするために必要とされるべき。 https://realm.github.io/SwiftLint/unused_import.html unused_import // bad import Foundation class A { } // good import Foundation @objc class A { } Vertical Parameter Alignment On Call 関数を呼び出す時に、パラメータが複数行にまたがる場合は垂直に揃えるべき。 https://realm.github.io/SwiftLint/vertical_parameter_alignment_on_call.html vertical_parameter_alignment_on_call // bad foo(param1: 1, param2: bar param3: false, param4: true) foo(param1: 1, param2: bar param3: false, param4: true) foo(param1: 1, param2: bar param3: false, param4: true) // good foo(param1: 1, param2: bar param3: false, param4: true) foo(param1: 1, param2: bar) foo(param1: 1, param2: bar param3: false, param4: true) Vertical Whitespace Between Cases switch文内のcaseとcaseの間は空白行を1行含むべき。 https://realm.github.io/SwiftLint/vertical_whitespace_between_cases.html vertical_whitespace_between_cases // bad switch x { case .valid: print("x is valid") case .invalid: print("x is invalid") } // good switch x { case .valid: print("x is valid") case .invalid: print("x is invalid") } Vertical Whitespace before Closing Braces 中括弧を閉じる前に空白行を入れるべきではない。 https://realm.github.io/SwiftLint/vertical_whitespace_closing_braces.html vertical_whitespace_closing_braces // bad [ 1, 2, 3 ] do { print("x is 5") } // good [ 1, 2, 3 ] do { print("x is 5") } Vertical Whitespace after Opening Braces 中括弧を開いた後に空白行を入れるべきではない。 https://realm.github.io/SwiftLint/vertical_whitespace_opening_braces.html vertical_whitespace_opening_braces // bad [ 1, 2, 3 ] do { print("x is 5") } // good [ 1, 2, 3 ] do { print("x is 5") } XCTest Specific Matcher XCTAssertEqualやXCTAssertNotEqualより、特定のXCTestマッチャーを使うべき。 https://realm.github.io/SwiftLint/xct_specific_matcher.html xct_specific_matcher // bad XCTAssertEqual(foo, true) XCTAssertEqual(foo, false) XCTAssertEqual(foo, nil) XCTAssertNotEqual(foo, true) XCTAssertNotEqual(foo, false) // good XCTAssertFalse(foo) XCTAssertTrue(foo) XCTAssertNil(foo) XCTAssertNotNil(foo) XCTAssertEqual(foo, 2) XCTAssertNotEqual(foo, "false") Yoda condition rule 変数は比較演算子の左側に配置し、定数は右側に配置すべき。 https://realm.github.io/SwiftLint/yoda_condition.html yoda_condition // bad if 42 == foo {} // good if foo == 42 {} おわりに こうして全体的にルールを見てみると、Opt-inのルールにも意外と使えそうというかDefault(有効)になってていいと思うルールも結構ありました。Conditional Returns on Newline(guardのreturnは改行すべきというルール)などについてはよく論争が起きている気がするので、チーム全体で吟味して有効/無効にしても良さそうだなと特に思います。 全ルールをまとめてみると「こういう書き方もあるんだな」って気づきがあるのでおすすめです。(因みにまとめるのに数日かかりました。) 参考 こちら参考にさせていただきました?‍♂️ - SwiftLintのRules全まとめ - SwiftLintの全ルール一覧(Swift 4.2版) - SwiftLintで追加・変更・廃止されたルールまとめ(Swift 4.2→5.1.2版) - SwiftLintFramework Reference Rule Directory Reference
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】タップした時に追従してくるViewを作成する

はじめに このような感じで、タップした位置に追従してくるようなViewを作成したいと思います。 実装 コピペで作成できます。 final class ViewController: UIViewController { private var myView: UIView! override func viewDidLoad() { super.viewDidLoad() myView = UIView() myView.frame.size = CGSize(width: 100, height: 100) myView.center = self.view.center myView.backgroundColor = .red myView.isUserInteractionEnabled = true self.view.addSubview(myView) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { if touches.first!.view == self.view { let after = touches.first!.location(in: self.view) let before = myView.center let deltaX = after.x - before.x let deltaY = after.y - before.y UIView.animate(withDuration: 0.3) { self.myView.transform = CGAffineTransform(translationX: deltaX, y: deltaY) } } } } 解説 touchesBeganはタップした時に呼ばれるメソッドです。(touchesBegan) 一番はじめにタップした箇所の座標を取得します。これがViewの目的地の座標というわけです。 let after = touches.first!.location(in: self.view) 移動前のViewの座標も取得する必要があります。 let before = myView.center xとyの変化量をそれぞれ求めます。(変化量というのは、どれぐらい値が増減したかを表すものです) 例えば、移動前の座標(10, 300)から移動後の座標(30, 200)に移動した場合のxとyの変化量Δx, Δyは Δx = 30 - 10 = 20 Δy = 200 - 300 = -100 よって、変化量は(20, -100)となるわけです。 let deltaX = after.x - before.x let deltaY = after.y - before.y そして、求めた変化量がViewが移動するべき移動量になるので、CGAffineTransformでアニメーション付きで移動させてあげます。(CGAffineTransform) UIView.animate(withDuration: 0.3) { self.myView.transform = CGAffineTransform(translationX: deltaX, y: deltaY) } おわりに おわりです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】UITextViewにプレースホルダーをつける

はじめに テキストビューにもテキストフィールドのようにプレースホルダーをつけたい時があるかと思います。 なので、今回はテキストビューにプレースホルダーをつけるにはどうすればいいのかを解説します。 完成するもの GitHub 実装 まずは、PlaceTextViewというカスタムクラスを作るためのswiftファイルとxibファイルを作成してください。 コードを書いていきます。 final class PlaceTextView: UITextView { var placeHolder: String = "" { willSet { self.placeHolderLabel.text = newValue self.placeHolderLabel.sizeToFit() } } private lazy var placeHolderLabel: UILabel = { let label = UILabel() label.lineBreakMode = .byWordWrapping label.numberOfLines = 0 label.font = self.font label.textColor = .gray label.backgroundColor = .clear label.translatesAutoresizingMaskIntoConstraints = false self.addSubview(label) return label }() override func awakeFromNib() { super.awakeFromNib() NotificationCenter.default.addObserver(self, selector: #selector(textDidChanged), name: UITextView.textDidChangeNotification, object: nil) NSLayoutConstraint.activate([ placeHolderLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 7), placeHolderLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 7), placeHolderLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5), placeHolderLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 5) ]) } @objc private func textDidChanged() { let shouldHidden = self.placeHolder.isEmpty || !self.text.isEmpty self.placeHolderLabel.alpha = shouldHidden ? 0 : 1 } } Main.storyboardでUITextViewを配置します。 配置したUITextViewのカスタムクラスに先ほど書いたカスタムクラスを継承させます。n ViewControllerではどのようなプレースホルダーを表示させるのかを設定します。 final class ViewController: UIViewController { @IBOutlet private weak var textView: PlaceTextView! override func viewDidLoad() { super.viewDidLoad() textView.placeHolder = "入力してください。" } } 解説 表示させるプレースホルダーのテキストをこちらで指定し、入ってきた値をラベルのテキストにします。 var placeHolder: String = "" { willSet { self.placeHolderLabel.text = newValue self.placeHolderLabel.sizeToFit() } } ラベルの設定をします。 private lazy var placeHolderLabel: UILabel = { let label = UILabel() label.lineBreakMode = .byWordWrapping label.numberOfLines = 0 label.font = self.font label.textColor = .gray label.backgroundColor = .clear label.translatesAutoresizingMaskIntoConstraints = false self.addSubview(label) return label }() オブザーバーを追加します。UITextView.textDidChangeNotificationなので、このオブザーバーはテキストが入力または消去された時に通知されます。 NotificationCenter.default.addObserver(self, selector: #selector(textDidChanged), name: UITextView.textDidChangeNotification, object: nil) 先ほど追加したオブザーバーで通知されるたびに呼ばれるメソッドです。 初回で入力されたまたは全てテキストが消去されたときにalphaを変更することで表示非表示を切り替えます。 @objc private func textDidChanged() { let shouldHidden = self.placeHolder.isEmpty || !self.text.isEmpty self.placeHolderLabel.alpha = shouldHidden ? 0 : 1 } オートレイアウトの指定です。これを指定しないと左上つめつめになってしまうので余白として制約を貼りました。 NSLayoutConstraint.activate([ placeHolderLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 7), placeHolderLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 7), placeHolderLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5), placeHolderLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 5) ]) あとはViewControllerでIBOutlet接続をしてプレースホルダーを設定してあげましょう。 final class ViewController: UIViewController { @IBOutlet private weak var textView: PlaceTextView! override func viewDidLoad() { super.viewDidLoad() textView.placeHolder = "入力してください。" } } おわりに 公式でサポートしてほしいですね、、、
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WIP] WWDC2021のCoding & Designチャレンジをしながらの学びメモ

この記事の注意点 学んだことをアウトプットするというより備忘録的要素強めのメモになります スクリーンショットや動画など、WWDCには開発者としてのNDAがつきものですが、この記事はあくまで全員が見れる情報に関してのみの記述になります また今回のWWDCのCoding & Designチャレンジには公式に#WWDC21Challengesというハッシュタグが用意されています。そのため、このハッシュタグにて公開されているキャプチャと同等レベルであればNDAには抵触しないとみなします。 WWDC2021にはたくさんのチャレンジが用意されていますが、今回は独断と偏見でそのうちの一部にだけ挑戦しています。その他のチャレンジが気になる方はぜひWWDC公式サイトなどから見てみてください。 かなりボリューミーになることが予想されるためWIPをつけている間は複数回に分けて更新していきます。 挑戦したチャレンジ 全チャレンジはこちら サイトはアルファベット順になっていますが、今回はDeveloperアプリの順番でやっていきます。 Build an app that recognizes custom audio through ShazamKit Create your first 3D model with Object Capture Animated artistry in SwiftUI Throwback with SwiftUI Framework Freestyle Memgraph capture the flag Focus on Focus in SwiftUI Prototype with SwiftUI Build an app that recognizes custom audio through ShazamKit ShazamKit ... Shazamのカタログやカスタムカタログで音楽聞き取りができる Signatureというものを音から抽出してそのマッチングを行う SHSession, SHSignatureGenerator Delegateでマッチしたかどうかの結果を受け取る SHMediaLibraryを使ってShazamライブラリに追加できる AppleMusicとの連携をしたければMusicKitを使う サンプルコード SHSignatureGeneratorにバッファーを渡すと音声がsignatureになる Part1からPart3まであるのに違いがない...これはなんだ...そしてiPadだけ対応のサンプルだった... ビデオの特定の音声をsignature化しておくことで音声に合わせてクイズを出題するアプリが作れる SwiftUI的なフォルダ構成はApp/Data/Extensions/Resources/Views シミュレータでも実行可能 任意の状態から音でアプリの状態を変えることができる、Shazamってそういう技術だけど応用例があるとすごい 実際に使えそうなシチュエーション 自分でやるのはデータを用意するのが必要だし時間かかるなと思ったのでアイデア出しだけ サンプルのような教材動画の副教材アプリ 脱出ゲームのギミック(部屋を進んでいくと流れている音声が変わる→制限時間の同期や音楽を使った謎など) インタラクティブ広告(アプリを触っている最中、特定の場所にいくとそこの音で広告がポップアップする→自然とそのお店に入ってみようかなという動機付けに使えるのでは?) Create your first 3D model with Object Capture サンプルアプリ こっちは全部ベタ書きだな 3Dモデルを作るにはmacOSも必要らしい iOS: PhotogrammetrySession(Image Depth Gravityほか)→Connect output stream RealityKitで扱う Model File(URL), ModelEntity, BoundingBox(RealityKit) 椅子にGameSir T4Proを置いて撮ってみた、HEIC, TXT, TIFファイルで構成されるみたい、iPhoneのドキュメントから確認できる 3Dモデル化はMonteleyが必要らしいので用意できたらやります Animated artistry in SwiftUI ignoresSafeArea(edges:) .background(.regularMaterial), .thinMaterial .foregroundStyle(.secondary) .safeAreaInset(edge: .bottom) GeometryReader, drawingGroup() withAnimation Canvas { context, size in } Canvasのかきごこち、だいぶp5とかに近そう context.fillとかある CanvasをTimelineViewで囲ってあげるとアニメーションもできそう .accessibilityLabelもつけてあげる大事さ やってみたものはこちら 慣れは必要だけど感覚としてはProcessing的な書き方をSwiftでもできるようになったという感じ Throwback with SwiftUI 指示通り乱数くじ引きをしたところ1989年のUIを再現することに(生まれてない...笑) ref: https://www.youtube.com/watch?v=IfxTRyrEUvo 1989年はMachintosh Portableの発売年、ノートパソコンの走りのようなものだったのかな (特に参照セッションがないので時間ができた時に作って追記します) Framework Freestyle サンプルから100行以内のコードと新しいフレームワークを組み合わせて何かを作ってあげるチャレンジ (こちらも参照セッションがないので時間ができた時に作って追記します) Memgraph capture the flag 慣れない分野のせいでめっちゃ雑メモです。検索かかってしまったら申し訳ないです... symbolication atosツール デバッグしたときに原因を特定しやすくする dSYM translate runtime addresses to file addresses Linkerの挙動理解な Headerと __TEXTと __DATA otool -lとgrepを使う ASLR slide, S=L-A vmmapツール -tVオプション atosの-oと-lオプション あんまりわかってないけど途中のまとめ App binaries and frameworks are Mach-O files Kernel adds ASLR slide to determine load address ASLR slide equation DWARFはいろんな情報を含められる symbols -onlyFuncStartsData nmコマンド Stripビルドセッティング
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】PageViewControllerで画面遷移する

はじめに 今回はPageViewControllerを使ってスワイプした時に画面遷移をさせてみます。 GitHub 実装 一番左がPageViewControllerで、赤色がFirstViewController, 青色がSecondViewController, 緑色がThirdViewControllerです。 そして、PageViewControllerというUIPageViewControllerを継承したクラスを作ります。 final class PageViewController: UIPageViewController 色のついたViewControllrにStoryboardIDとSwiftファイルを用意してください。 全体のコードは以下の通りです。 final class PageViewController: UIPageViewController { private var controllers = [UIViewController]() override func viewDidLoad() { super.viewDidLoad() setupPageViewController() } private func setupPageViewController() { let firstVC = storyboard?.instantiateViewController( withIdentifier: "FirstViewController" ) as! FirstViewController let secondVC = storyboard?.instantiateViewController( withIdentifier: "SecondViewController" ) as! SecondViewController let thirdVC = storyboard?.instantiateViewController( withIdentifier: "ThirdViewController" ) as! ThirdViewController controllers = [firstVC, secondVC, thirdVC] setViewControllers([controllers[0]], direction: .forward, animated: true, completion: nil) self.dataSource = self } } extension PageViewController: UIPageViewControllerDataSource { func presentationCount(for pageViewController: UIPageViewController) -> Int { return controllers.count } func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { if let index = controllers.firstIndex(of: viewController), index < controllers.count - 1 { return controllers[index + 1] } return nil } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { if let index = controllers.firstIndex(of: viewController), index > 0 { return controllers[index - 1] } return nil } } 解説 表示するためのViewControllerを配列に格納して管理します。 let firstVC = storyboard?.instantiateViewController( withIdentifier: "FirstViewController" ) as! FirstViewController let secondVC = storyboard?.instantiateViewController( withIdentifier: "SecondViewController" ) as! SecondViewController let thirdVC = storyboard?.instantiateViewController( withIdentifier: "ThirdViewController" ) as! ThirdViewController controllers = [firstVC, secondVC, thirdVC] // 最初の画面を設定 setViewControllers([controllers[0]], direction: .forward, animated: true, completion: nil) // ここでデリゲートも設定しておく self.dataSource = self setViewControllers(...)を使い、最初に表示する画面を設定します。 setViewControllers setViewControllers([controllers[0]], direction: .forward, animated: true, completion: nil) UIPageViewControllerDataSourceを見ていきます。 ページ数を返します。 func presentationCount(for pageViewController: UIPageViewController) -> Int { return self.controllers.count } 左スワイプ(Before)した時の表示する画面を返します。 func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { if let index = controllers.firstIndex(of: viewController), index < controllers.count - 1 { return controllers[index + 1] } return nil } 右スワイプ(After)した時の表示する画面を返します。 func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { if let index = controllers.firstIndex(of: viewController), index > 0 { return controllers[index - 1] } return nil } おわりに PageViewController PageViewControllerを使った画面遷移をしてみました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む