20210615のSwiftに関する記事は5件です。

【Swift】if let 複数

どういうことか たとえばひとつのラベルに2つの値をアンラップしたい。 下の例ではチケットの有効期限を同じ文字列内にアンラップして出力している。 textLabel.text = "有効期限:     \(ticket?.startDate ?? "")〜\(ticket?.limitDate ?? "")" これを if let でアンラップする方法に修正したい。 複数の宣言方法 // カンマで区切る if let startDate = ticket?.startDate, let limitDate = ticket?.limitDate { textLabel.text = "有効期限: \(dateFrom)〜\(dateTo)" } , で区切って宣言するだけでいい。 おわり(´・ω・`)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftのジェネリクスでシンプルな双方向バインドのMVVM

Github 全体図 プロトコルではなくジェネリクスでModelを切り替える。 クラスの継承関係はViewとViewModelの連携を担うLinkViewControllerは、 Modelのジェネリクスを保持します。Modelをジェネリクス化することで、 Viewとロジック(Model)を分けたのですが、ジェネリクスはextensionをサポートせず、 BaseViewControllerの下で定義する必要がありました。 BaseViewController << LinkViewController << 継承元ViewController LinkViewController.swift // // LinkViewController.swift // CalendarIOs // // Created by 脇坂亮汰 on 2021/04/10. // Copyright © 2021 脇坂亮汰. All rights reserved. // import Foundation class LinkViewController<T: BaseModel>: BaseViewController { internal var viewModel: ViewModel<T>! required init?(coder: NSCoder) { super.init(coder: coder) viewModel = ViewModel<T>() NotificationCenter.default.addObserver( self, selector: #selector(notificationFromModel(_:)), name: .actionFromModel, object: nil) } @objc func notificationFromModel(_ notification: Notification) { guard let values = notification.object as? ( ViewModel<T>, [ActionFromModel:Any]), values.0 == viewModel else { return } let action = values.1.keys.first! let params = values.1.values.first! as! [String:Any] let log = params["log"] as? Bool ?? false if log { print("\(className) ActionFromModel action=\(action) params=\(params)") } receiveAction(action, params: params) } override func sendAction(_ action: ActionFromView, params: [String:Any] = [:], broadcast: Bool = false) { var object: (ViewModel<T>?, [ActionFromView:Any]) object.0 = broadcast ? nil : viewModel object.1 = [action:params] NotificationCenter.default.post(name: .actionFromView, object: object) } override func receiveAction(_ action: ActionFromModel, params: [String:Any]) { super.receiveAction(action, params: params) } } ViewControllerの基本クラスの継承方法 LinkViewControllerのジェネリクスにModelクラスを指定することで、 LinkViewControllerはメンバ変数にViewModelを保持するようになり、 ViewModelはメンバ変数にジェネリクスで定義したModelを保持します。 VerificationMenuViewController.swift // // VerificationMenuViewController.swift // CalendarIOs // // Created by 脇坂亮汰 on 2021/05/15. // Copyright © 2021 脇坂亮汰. All rights reserved. // import Foundation import UIKit class VerificationMenuVCMenuTableViewCell: UITableViewCell, PVCellCornerViewHighlight { @IBOutlet weak var cornerView: UIView! @IBOutlet weak var _textLabel: UILabel! override func setHighlighted(_ highlighted: Bool, animated: Bool) { updateBackground(highlighted: highlighted, animated: animated) } override func setSelected(_ selected: Bool, animated: Bool) { updateBackground(highlighted: selected, animated: animated) } } class VerificationMenuViewController: LinkViewController<VerificationMenuVCModel> { typealias S = VerificationMenuVCMenuTableSection @IBOutlet weak var menuTableView: UITableView! var sections: [S] = [] var selectionIndexPath: IndexPath! let sectionHeaderHeight: CGFloat = 45 override func receiveAction(_ action: ActionFromModel, params: [String : Any]) { super.receiveAction(action, params: params) switch action { case .dataSetting: sections = params["sections"] as? [S] ?? [] default: break } } override func viewDidLoad() { super.viewDidLoad() menuTableView.delegate = self menuTableView.dataSource = self } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if let indexPath = selectionIndexPath { selectionIndexPath = nil menuTableView.deselectRow(at: indexPath, animated: true) } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { super.tableView(tableView, didSelectRowAt: indexPath) selectionIndexPath = indexPath } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return sectionHeaderHeight } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sections[section].title } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sections[section].cells.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell( withIdentifier: "VerificationMenuVCMenuTableViewCell", for: indexPath) as! VerificationMenuVCMenuTableViewCell cell._textLabel?.text = sections[indexPath.section].cells[indexPath.row].title return cell } } ViewとModelの関係 UIKitのControllerは上の階層のControllerがインスタンスを管理します。 そのため、UIViewController、UINavigationController、 UITabBarControllerは限定的なスコープで生きる必要があり、 内部変数のクラスに処理を委譲する構成にする必要がありました。 また、同じUIViewControllerを一部のタブや、上の階層のUINavigationで、 使用するケースが想定されます。全ての画面に共通の通知を行なった場合、 同じ通知を受け取るとどちらに向けた通知なのか識別できなくなります。 Modelの通知形式は、自分の親や、子供の1人にピンポイントで通知を行う、 必要があります。 UIViewControllerの構成をEnum管理する。 VCStructureIndexはUIViewControllerやUINavigationControllerの、 構成を示しています。UINavigationControllerの構成の中に他のVCStructureIndex、 の構成を取り入れることを可能にしています。 generateVCメソッドがView、Modelまたは、他のViewのModelへの双方向バインドを可能にする、 PVLinkableなインスタンスを生成します。PVLinkableはUIViewControllerの、 要件を満たしているため独立したUIViewControllerの生成でもあります。 また、Protocol Oriented ProgrammingでUIViewController、UINavigationController、UITabBarControllerで共通のメソッドを汎用化しています。 VCManager.swift // // VCManager.swift // CalendarIOs // // Created by 脇坂亮汰 on 2021/04/08. // Copyright © 2021 脇坂亮汰. All rights reserved. // import Foundation import UIKit enum VCStructureIndex { case verificationMenuViewNavigation case verificationMenuView case navigationTestMenuViewNavigation case navigationTestMenuView case navigationTestTabBar case navigationTestTabBarItem1ViewNavigation case navigationTestTabBarItem1View case navigationTestTabBarItem2ViewNavigation case navigationTestTabBarItem2View case navigationTestTabBarItem3ViewNavigation case navigationTestTabBarItem3View case navigationTestTabBarItem4ViewNavigation case navigationTestTabBarItem4View case navigationTestTabBarItem5ViewNavigation case navigationTestTabBarItem5View struct VCStructure { enum ControllerType { case navigation case tabBar case view } init(type: ControllerType, storyboardName: String, storyboardId: String! = nil) { self.type = type self.storyboardName = storyboardName self.storyboardId = storyboardId } var type: ControllerType var storyboardName: String var storyboardId: String! } struct TabBarStructure { var iconImage: UIImage! var title: String } struct NavigationStructure { var hidden: Bool var title: String! } var vCStructure: VCStructure { typealias S = VCStructure switch self { case .verificationMenuViewNavigation: return S(type: .navigation, storyboardName: "NavigationController", storyboardId: "_NavigationController") case .verificationMenuView: return S(type: .view, storyboardName: "VerificationMenuViewController") case .navigationTestMenuViewNavigation: return S(type: .navigation, storyboardName: "NavigationController", storyboardId: "_NavigationController") case .navigationTestMenuView: return S(type: .view, storyboardName: "NavigationTestMenuViewController") case .navigationTestTabBar: return S(type: .tabBar, storyboardName: "TabBarController") case .navigationTestTabBarItem1ViewNavigation, .navigationTestTabBarItem2ViewNavigation, .navigationTestTabBarItem3ViewNavigation, .navigationTestTabBarItem4ViewNavigation, .navigationTestTabBarItem5ViewNavigation: return S(type: .navigation, storyboardName: "NavigationController", storyboardId: "_NavigationController") case .navigationTestTabBarItem1View, .navigationTestTabBarItem2View, .navigationTestTabBarItem3View, .navigationTestTabBarItem4View, .navigationTestTabBarItem5View: return S(type: .view, storyboardName: "NavigationTestMenuViewController") } } var navigationStructure: NavigationStructure { typealias S = NavigationStructure switch self { case .verificationMenuView: return S(hidden: false, title: "検証メニュー") case .navigationTestMenuView: return S(hidden: false, title: "ナビゲーションテスト") case .navigationTestTabBarItem1View: return S(hidden: false, title: "タブバー(タブ1)") case .navigationTestTabBarItem2View: return S(hidden: false, title: "タブバー(タブ2)") default: return S(hidden: true, title: nil) } } var tabBarStructure: TabBarStructure { typealias S = TabBarStructure switch self { case .navigationTestTabBarItem1ViewNavigation: return S(iconImage: UIImage(systemName: "cloud"), title: "タブ1") case .navigationTestTabBarItem2ViewNavigation: return S(iconImage: UIImage(systemName: "cloud"), title: "タブ2") default: return S(iconImage: UIImage(systemName: "icloud.and.arrow.down"), title: "タイトル") } } var setViewControllers: [VCStructureIndex] { switch self { case .verificationMenuViewNavigation: return [.verificationMenuView] case .navigationTestMenuViewNavigation: return [.navigationTestMenuView] case .navigationTestTabBar: return [.navigationTestTabBarItem1ViewNavigation, .navigationTestTabBarItem2ViewNavigation, .navigationTestTabBarItem3ViewNavigation, .navigationTestTabBarItem4ViewNavigation, .navigationTestTabBarItem5ViewNavigation] case .navigationTestTabBarItem1ViewNavigation: return [.navigationTestTabBarItem1View] case .navigationTestTabBarItem2ViewNavigation: return [.navigationTestTabBarItem2View] case .navigationTestTabBarItem3ViewNavigation: return [.navigationTestTabBarItem3View] case .navigationTestTabBarItem4ViewNavigation: return [.navigationTestTabBarItem4View] case .navigationTestTabBarItem5ViewNavigation: return [.navigationTestTabBarItem5View] default: return [] } } } class VCManager { static func generateVC(_ index: VCStructureIndex) -> PVLinkable { let structure = index.vCStructure let storyboardName = structure.storyboardName let storyboardId = structure.storyboardId ?? structure.storyboardName let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main) let vc = storyboard.instantiateViewController( withIdentifier: storyboardId) as! PVLinkable vc.vCIndex = index if index.vCStructure.type == .navigation && index.setViewControllers.count > 0 { (vc as! UINavigationController) .setViewControllers(index.setViewControllers.enumerated().map({ (i, index) in let vc = generateVC(index) vc.sendAction(.navigationStructure, params: ["navigation_index": i], broadcast: false) return vc }), animated: false) } if index.vCStructure.type == .tabBar && index.setViewControllers.count > 0 { let vc = vc as! UITabBarController let vcs: [UIViewController] = index.setViewControllers.enumerated().map { (i, type) -> UIViewController in let tbs = type.tabBarStructure let innerVc = generateVC(type) let image: UIImage! = tbs.iconImage == nil ? nil : tbs.iconImage innerVc.sendActionChildren(.tabBarStructure, params: ["tab_bar_index": i]) innerVc.tabBarItem = UITabBarItem(title: tbs.title, image: image, selectedImage: nil) return innerVc } vc.setViewControllers(vcs, animated: false) } return vc } } ViewModel ViewとViewModel、ViewModelとModelの連携はインスタンスを比較して、 対象のクラスにしか届かないようにする必要があります。 通知チェーン 一部通知では、Enumの属性に同じEnumを指定できるようにしています。 これは例えば、自分のControllerから見て、親や、特定の子供向けに、 さらにその場所から見た相対的な通知を柔軟に送ることを可能にしています。 sendAction(.parentAction(.pushNavigation), params: ["vc_index": VCStructureIndex.navigationTestTabBar, "input_params": params]) ViewModel.swift // // ViewModel.swift // CalendarIOs // // Created by 脇坂亮汰 on 2020/07/23. // Copyright © 2020 脇坂亮汰. All rights reserved. // import Foundation enum ActionFromView: Hashable { case selfAction(ActionFromModel) case navigationStructure case tabBarStructure case inputParams case loadView case viewDidLoad case viewWillAppear case viewDidAppear case viewWillDisappear case viewDidDisappear case touchUpInside case presentingCompletion case dismissalStating case dismissalCancel case dismissalCompletion case scrollViewDidScroll case scrollViewWillBeginDragging case scrollViewDidEndDragging case scrollViewDidEndDecelerating case tableViewDisplaying case tableViewReleasing case tableViewSelection case collectionViewDisplaying case collectionViewReleasing case collectionViewSelection case calendarFunc(CalendarFunctionActionFromView) case defaultCalendarVc(DefaultCalendarVcActionFromView) } indirect enum ActionFromModel: Hashable { case parentAction(ActionFromModel) case presentingVCAction(ActionFromModel) case tabBarAction(ActionFromModel) case navigationAction(ActionFromModel) case childAction(VCStructureIndex, ActionFromModel) case replaceRootViewController case definesPresentationContext case present case dismiss case pushNavigation case popNavigation case pushParentNavigation case popParentNavigation case dataSetting case reloadData case calendarFunc(CalendarFunctionActionFromModel) } enum CalendarFunctionActionFromView { case screenInfoRequesting case dayCellBeginTouch } enum CalendarFunctionActionFromModel { case screensOverwriting case eventsOverwriting case screenResponsing case screenPositionSettingAdding case eventCellColCountSharing } enum DefaultCalendarVcActionFromView { case configButtonTapping } class ViewModel<T: BaseModel>: NSObject, EClass { private(set) var model: BaseModel! override init() { super.init() print("init \(className)!!") model = T() NotificationCenter.default.addObserver( self, selector: #selector(notificationFromView(_:)), name: .actionFromView, object: nil) NotificationCenter.default.addObserver( self, selector: #selector(notificationFromModel(_:)), name: .actionFromModel, object: nil) } deinit { print("deinit \(className)!!") } @objc func notificationFromView(_ notification: Notification) { guard let values = notification.object as? ( ViewModel?, [ActionFromView:Any]), values.0 == self || values.0 == nil else { return } let action = values.1.keys.first! let params = values.1.values.first! as! [String:Any] let log = params["log"] as? Bool ?? false if log { print("\(className) ActionFromView action=\(action) params=\(params)") } sendAction(action, params: params) } @objc func notificationFromModel(_ notification: Notification) { guard let values = notification.object as? ( BaseModel?, [ActionFromModel:Any]), values.0 == model || values.0 == nil else { return } let action = values.1.keys.first! let params = values.1.values.first! as! [String:Any] let log = params["log"] as? Bool ?? false if log { print("\(className) ActionFromModel action=\(action) params=\(params)") } sendAction(action, params: params) } func sendAction(_ action: ActionFromModel, params: [String:Any]) { var object: (ViewModel<T>, [ActionFromModel:Any]) object.0 = self object.1 = [action:params] NotificationCenter.default.post(name: .actionFromModel, object: object) } func sendAction(_ action: ActionFromView, params: [String:Any]) { var object: (BaseModel, [ActionFromView:Any]) object.0 = model object.1 = [action:params] NotificationCenter.default.post(name: .actionFromView, object: object) } } ModelはViewControllerの処理タイミングも制御する。 Viewにも基本クラスのViewDidLoadがあり、また、 ModelにもViewの基本クラスから送られるViewDidLoadがあります。 レイアウト(UIKit)に関わる対応はView、それ以外のAPI呼び出しや、 Viewを制御する上で表示タイミングを制御したい場合は、Modelの ViewDidLoadを使用するように考えています。 // // BaseModel.swift // CalendarIOs // // Created by 脇坂亮汰 on 2020/07/23. // Copyright © 2020 脇坂亮汰. All rights reserved. // import Foundation class BaseModel: NSObject, EClass { required override init() { super.init() print("init \(className)!!") } deinit { print("deinit \(className)!!") } func sendAction(_ action: ActionFromModel, params: [String:Any], broadcast: Bool = false) {} func receiveAction(_ action: ActionFromView, params: [String:Any]) { switch action { case .selfAction(let action): sendAction(action, params: params) default: break } } } // // LinkModel.swift // CalendarIOs // // Created by 脇坂亮汰 on 2021/04/10. // Copyright © 2021 脇坂亮汰. All rights reserved. // import Foundation class LinkModel: BaseModel { required init() { super.init() NotificationCenter.default.addObserver( self, selector: #selector(notificationFromView(_:)), name: .actionFromView, object: nil) } @objc func notificationFromView(_ notification: Notification) { guard let values = notification.object as? ( BaseModel, [ActionFromView:Any]), values.0 == self else { return } let action = values.1.keys.first! let params = values.1.values.first! as! [String:Any] let log = params["log"] as? Bool ?? false if log { print("\(className) ActionFromView action=\(action) params=\(params)") } receiveAction(action, params: params) } override func sendAction(_ action: ActionFromModel, params: [String:Any] = [:], broadcast: Bool = false) { var object: (BaseModel?, [ActionFromModel:Any]) object.0 = broadcast ? nil : self object.1 = [action:params] NotificationCenter.default.post(name: .actionFromModel, object: object) } override func receiveAction(_ action: ActionFromView, params: [String:Any]) { super.receiveAction(action, params: params) } } // // VerificationMenuVCModel.swift // CalendarIOs // // Created by 脇坂亮汰 on 2021/05/15. // Copyright © 2021 脇坂亮汰. All rights reserved. // import Foundation class VerificationMenuVCModel: LinkModel { typealias S = VerificationMenuVCMenuTableSection typealias C = VerificationMenuVCMenuTableCell let sections: [S] = [S(title: "画面遷移", cells: [C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest), C(title: "ナビゲーションテスト", actionId: .navigationTest)])] override func receiveAction(_ action: ActionFromView, params: [String:Any]) { super.receiveAction(action, params: params) switch action { case .viewDidLoad: sendAction(.dataSetting, params: ["sections":sections]) case .tableViewSelection: guard let indexPath = params["index_path"] as? IndexPath, let actionId = sections[indexPath.section].cells[indexPath.row].actionId else { return } switch actionId { case .navigationTest: sendAction(.parentAction(.pushNavigation), params: ["vc_index": VCStructureIndex.navigationTestMenuView, "input_params": ["previous_vc_index_path":indexPath, "pushed_navigation":true]]) } default: break } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】Metal Best Practicesの解説(3) リソースオプション

Metal Best Practicesは、iOS/MacOS/tvOSのAPIであるMetalを用いた設計のベストプラクティスガイドです。 本稿では、何回かに分けてこのガイドを読み解き、コード上での実験を交えて解説していきます。 読んでそのまま理解できそうなところは飛ばしますので、原文を読みながら原文のガイドとしてご利用下さい。 また、iOSの記事なので他のOS(MacOS, tvOS)についての記載は割愛します。 他の記事の一覧は、初回記事より参照下さい。 Resource Options (リソースオプション) ベストプラクティス:適切なリソースストレージモードとテクスチャ使用オプションを設定します。 リソースストレージモードについて iOS, tvOSの場合は次の2種類があります。このように、CPUとGPUがメモリを共有するモデルをユニファイドメモリモデルといいます。 モード 説明 Shared モード CPU と GPU の両方にアクセス可能 Private モード GPU のみにアクセス可能 【iOS, tvOSのリソースストレージモードの概要図】 そして、iOSにおけるリソースストレージモードの選択は、通常はSharedモードが正しい選択であるとのこと。プライベートモードは、CPU がリソースにアクセスしない場合にのみ選択する。 適切なテクスチャ使用フラグの設定 Metalは、その使用目的に基づいて、特定のテクスチャのGPU操作をできる。事前に知っている場合は、必ず明示的なテクスチャ使用オプションを宣言します。 Unknownオプションに依存しないこと。このオプションはテクスチャに最も柔軟性を提供するが、かなりのパフォーマンスコストが発生します。 コードで検証してみる テクスチャの使用フラグの設定によってどの程度パフォーマンスに違いが出るのか検証してみます。 こちらのリポジトリにサンプルコードがあります。 サンプルコードの中に、Particleというサンプルがあるのでこれを改変しながら計測していきます。 (実行イメージ) テクスチャの使用フラグを設定する箇所の抜粋です。コメントにあるとおり、texDesc.usageの設定をUnknownとする場合と明示的に指定する場合の両方を用意し、実行するときに片方をコメントアウトしながら計測します。 ParticleMetalView.swift func initTexture() { func makeRenderTexture() -> MTLTexture { let texDesc = MTLTextureDescriptor() texDesc.width = 100//(parent.mtkView.currentDrawable?.texture.width)! texDesc.height = 100//(parent.mtkView.currentDrawable?.texture.height)! texDesc.depth = 1 texDesc.textureType = MTLTextureType.type2D texDesc.storageMode = .private texDesc.pixelFormat = .bgra8Unorm texDesc.usage = .unknown // Unknown版 texDesc.usage = [MTLTextureUsage.renderTarget, MTLTextureUsage.shaderRead] // 明示指定版 return metalDevice.makeTexture(descriptor: texDesc)! } texture = makeRenderTexture() } 計測は、Metal System Traceを使います。使い方はこちらをご覧ください。 アプリを実行し、一定時間計測してから、GPUのサマリーでFragmentとVertexの所要時間を見てみましょう。 <Unknown版の結果> <明示指定版の結果> 「Avg CPU to GPU Latency」を見てください。VertexはなぜかUnknown版の方が早いですが、Fragmentは明示指定版の方が、1ms近くなっています。トータルでも明示指定版の方が0.77ms早い結果となりました。 設定を1つ変えるだけで1msも早くなるのなら、労力に対して結構いい感じの成果なんじゃないでしょうか? 結論 ベストプラクティスにあるとおり、テクスチャ使用フラグを明示するほうが効率的ということがわかりました。 最後に iOSを使ったARやML、音声処理などの作品やサンプル、技術情報を発信しています。 作品ができたらTwitterで発信していきますのでフォローをお願いします? Twitterは作品や記事のリンクを貼っています。 https://twitter.com/jugemjugemjugem Qiitaは、iOS開発、とくにARや機械学習、グラフィックス処理、音声処理について発信しています。 https://qiita.com/TokyoYoshida Noteでは、連載記事を書いています。 https://note.com/tokyoyoshida Zennは機械学習が多めです。 https://zenn.dev/tokyoyoshida
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftLintをコメントで無視する

はじめに 自分のメモのために記事を書いています。 初学者の方の参考になれば幸いです。 記事内容 SwiftLintをコードで無視するをメモとして書いています。 詳しい内容はGitHubのSwiftLint(Opt-In Rules)に書かれています。https://github.com/realm/SwiftLint コードでルールを無視する ルールは、次の形式のソースファイル内のコメントで無効にできます。 // swiftlint:disable <rule1> [<rule2> <rule3>...] ルールは、ファイルの終わりまで、またはリンターが一致する有効化コメントを確認するまで無効になります。 // swiftlint:enable <rule1> [<rule2> <rule3>...] 例えば: // swiftlint:disable colon let noWarning :String = "" // 変数名の直後のコロンに関する警告はありません! // swiftlint:enable colon let hasWarning :String = "" // 変数名の直後のコロンについて警告が生成されます allキーワードを含めると、リンターが一致する有効化コメントを確認するまで、すべてのルールが無効になります。 // swiftlint:disable all // swiftlint:enable all 例えば: // swiftlint:disable all let noWarning :String = "" // 変数名の直後のコロンに関する警告はありません! let i = "" // 短い識別子名についての警告もありません // swiftlint:enable all let hasWarning :String = "" // 変数名の直後のコロンについて警告が生成されます let y = "" // 短い識別子名について警告が生成されます 以下の三種類いずれかのコマンドを追加することで、有効範囲を変更することも可能です。 :previous // 前の行のみコマンドを適用します :this or :next // この(現在の)行または次の行のみコマンドを適用します 例えば // swiftlint:disable:next force_cast let noWarning = NSNumber() as! Int let hasWarning = NSNumber() as! Int let noWarning2 = NSNumber() as! Int // swiftlint:disable:this force_cast let noWarning3 = NSNumber() as! Int // swiftlint:disable:previous force_cast
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】UIAlertControllerと同じように使える自作ポップアップ

この記事は自分のブログの転記記事です。 元記事はこちら はじめに iOSアプリを作っているとき、ポップアップを出したいときにはUIAlertControllerを使っていますか? 最も簡単に実装できるのは良いのですが、少し見た目が物足りません。 そこでもう少し見た目にこだわった、かつUIAlertCotrollerと同じように使えるカスタムポップアップをコードのみで作ってみましたので紹介します。 ダークモードやアニメーション、多画面解像度にも対応済みです。使用、改変その他なんでもOKです。 注意事項 動作確認はiOS14でのみ行っています UIAlertControllerはStyleが.alertと.actionSheetの2つが使えますが、現段階では.alertのみの対応となっています UIAlertControllerではポップアップのボタン配置はいくつでもできますが、現段階では3つまでの対応となっています 最も簡単な使い方 ソースコードはGitHubに置いてあります。GitHub 1.「git clone」または「Download ZIP」でGitHubのソースコードをダウンロード 2.CustomAlertController -> CustomAlertExample -> CustomAlertController.swiftを自分のプロジェクトに追加 (ダウンロードしなくても、自分のプロジェクトでCustomAlertController.swiftを新規作成して、CustomAlertController.swiftの内容をコピペしても大丈夫です。) 3.ポップアップを表示させたい処理を行う部分で以下のように使ってください // カスタムアラートコントローラの作成 let alert = CustomAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert) // カスタムアラートアクションの作成 let action = CustomAlertAction(title: "OK", style: .default, handler: {_ in // このボタンを押したときの処理 print("キャンセル") }) // アラートにアクションを追加 alert.addAction(action) // modalPresentationStyleを.overFullScreenに設定 alert.modalPresentationStyle = .overFullScreen // modalTransitionStyleを.crossDissolveに設定 alert.modalTransitionStyle = .crossDissolve // 表示 present(alert, animated: true) アラートコントローラの作成 -> アラートアクションの作成 -> コントローラにアクションを追加 -> present()という流れはUIAlertControllerと変わらないのですが、present()の前に.modalPresentationStyle = .overFullScreen、.modalTransitionStyleを.crossDissolveに設定することを忘れないでください。 最も簡単なカスタマイズ方法 CustomAlertController.swiftの上部に定数を用意してあり、ここをいじることでポップアップの見た目を変えることができます。 どの定数がなにかはコメントアウトを参考にしてください。 ソースコード詳細解説 カスタムアラートアクション カスタムアラートクラスを自作しますので、アラートアクションも自作します。 イニシャライザを作成し、標準のUIAlertActionのようにCustomAlertAction(title: “OK”, style: .default, handler: {_ in })で作成できるようになります。 titleがボタンに表示される文字列、styleがボタンのスタイル、handlerにはボタン押下時の処理が入ります。 /// カスタムアラートアクションの種類 enum CustomAlertControllerAction { case cancel case `default` case destructive } /// カスタムアラートアクションクラス class CustomAlertAction : NSObject { /// タイトル var title:String /// スタイル var style:CustomAlertControllerAction /// ハンドラ var handler: ((_ action: CustomAlertAction)->())? /// 初期化 init(title: String, style: CustomAlertControllerAction, handler: ((_ action: CustomAlertAction) -> ())?){ self.title = title self.style = style self.handler = handler } } カスタムアラートコントローラ スタイルは現時点では.alertのみ。 /// カスタムアラートコントローラの種類(.actionSheetは現段階では非対応) enum CustomAlertControlerStyle { case alert } /// カスタムアラートコントローラクラス class CustomAlertController: UIViewController, UIViewControllerTransitioningDelegate { // 省略 } カスタムアラートコントローラの中身 イニシャライザを用意してUIAlertControllerと同じようにCustomAlertController(title: “タイトル”, message: “メッセージ”, preferredStyle: .alert)で生成できるようにする。 /// アラートタイトル var alertTitle: String? /// アラートメッセージ var alertMessage: String? /// 初期化 internal convenience init(title: String?, message: String?, preferredStyle: CustomAlertControlerStyle) { self.init() self.alertTitle = title self.alertMessage = message } ビューやラベル、ボタンなどをすべてコードから生成している。 // タイトルラベル var titleLabel = UILabel() // メッセージラベル var messageLabel = UILabel() // アラート背景 var alertBackground = UIView() /// アラートビュー var alertView = UIView() /// 1つ目のボタン var firstButton = UIButton() /// 2つ目のボタン var secondButton = UIButton() /// 3つ目のボタン var thirdButton = UIButton() UIAlertControllerと同じようにalert.addAction(action)でアクションを追加できるようにaddActionメソッドを用意。 現段階ではアクションは3つまでしか追加できない仕様のため、すでに3つ以上ある場合はエラーを出すようにしている。 /// アクション格納配列 var actions = [CustomAlertAction]() /// アクション追加 func addAction(_ action:CustomAlertAction){ // 現段階ではアクションは3つまで追加可能 if actions.count >= 3 { abort() } actions.append(action) } viewDidLoad()にてポップアップの土台となるviewの.autoresizesSubviewsをfalseに設定する。 これをしないと、ポップアップ土台のサイズを変えたときに中身のラベルやボタンの大きさまで変わってしまう。 /// ビューが読み込まれたときに呼ばれる処理 override func viewDidLoad() { // 自身でサイズを決めているためリサイズを切る alertView.autoresizesSubviews = false } setUIParts()が実際にポップアップを生成する関数。 こういうレイアウト系はviewDidLoad()やviewWillAppear()の中では正常に作動しないため、viewDidLayoutSubview()の中でsetUIParts()を呼ぶ。 しかしviewDidLayoutSubview()は複数回呼ばれる処理のため、lazyを使った変数で定義することで_ = initViewLayoutと呼ばれた最初の一回のみ処理を行うようにする。 /// サブビューをレイアウトした際に呼ばれる処理 override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() _ = initViewLayout } /// viewDidLayoutSubview()は複数回呼び出されるため最初の1度だけ呼び出されるようにlazyを利用 private lazy var initViewLayout : Void = { setUIParts() }() /// UI部品をセットする private func setUIParts() { // 省略 } あとはコードのゴリ押しでviewを作成する。慣れたらストーリーボードを使うより手っ取り早かったりする。 コード可読性のために別のプライベートメソッドへ切り出しを行っている。 /// UI部品をセットする private func setUIParts() { // 現在のY軸位置(作業用) var tmpY = 2 * INTERVAL // タイトル文言がある場合はタイトルラベルを作成 if self.alertTitle != nil { // タイトルラベルの作成 setUpTitle(tmpY: tmpY) // tmpYの更新 tmpY += titleLabel.frame.size.height + INTERVAL } // メッセージ文言がある場合はメッセージラベルを作成 if self.alertMessage != nil { // メッセージラベルの作成 setUpMessage(tmpY: tmpY) // tmpYの更新 tmpY += messageLabel.frame.size.height + INTERVAL } // 1つ目のアクションがある場合は1つ目のボタンを作成 if actions.count >= 1 { // 1つ目のボタンの作成 setUpButtons(button: firstButton, action: actions[0], tmpY: tmpY) // tmpYの更新 tmpY += BUTTON_HEIGHT + INTERVAL // 2つ目のアクションがある場合は2つ目のボタンを作成 if actions.count >= 2 { // 2つ目のボタンの作成 setUpButtons(button: secondButton, action: actions[1], tmpY: tmpY) // tmpYの更新 tmpY += BUTTON_HEIGHT + INTERVAL // 3つ目のアクションがある場合は3つ目のボタンを作成 if actions.count >= 3 { // 3つ目のボタンの作成 setUpButtons(button: thirdButton, action: actions[2], tmpY: tmpY) // tmpYの更新 tmpY += BUTTON_HEIGHT + INTERVAL } } } // アラートビューの作成 setUpAlertView(tmpY: tmpY) // アラート背景に追加 alertBackground.backgroundColor = BACKGROUND_COLOR alertBackground.addSubview(alertView) self.view = alertBackground } さいごに 今回はUIAlertControllerよりもう少し見た目にこだわった、かつUIAlertCotrollerと同じように使えるポップアップをコードのみで作ってみました。 UIImageViewで画像を追加したりなどもできます。皆さんのポップアップの参考になれば幸いです。 ご質問、不具合報告などありましたらTwitterまで(@ruemura3)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む