- 投稿日:2021-10-26T22:51:25+09:00
UIViewとUITableCellの共通化
はじめに こんにちは。 今回は、私がTableViewを使うプロジェクトでよく使っている、UIViewとUIViewCellの共通化について書きます。 UIViewとUIViewCellで同じようなデザインを使うことが多くて、この方法を見つけたときは感動しました。 TableViewがあるプロジェクトでは、ほぼ必ず使ってます。 ViewとCellの共通化が有効な場面 UITableViewのヘッダーにテーブルの項目タイトルをつけたい時 通常はViewとして使う部品をTableViewのCellとしても利用したい 実装 仕組み作り こんな感じで、プロトコルを1つ作ります。 CellとViewで共通利用したいカスタムビューにこのプロトコルを継承させればOKです。 extensionに定義したidentifierは、Cellをインスタンス化するときに使えるので、実装しておきます。 public protocol CellViewBinder { associatedtype ContentView: UIView static func contentView() -> ContentView static var identifier: String { get } } extension CellViewBinder { static var identifier: String { return String(describing: self) } } こっちがUIViewを継承したカスタムビューをUICellとして利用するために作るクラスです。 class GenericTableViewCell<View>: UITableViewCell where View: CellViewBinder, View.ContentView == View { let customView: View = .init(frame: .zero) override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) configure() } required init?(coder: NSCoder) { super.init(coder: coder) configure() } private func configure() { contentView.addSubview(customView) customView.translatesAutoresizingMaskIntoConstraints = false customView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true customView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true customView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true customView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true } } customViewに自分の使いたいカスタムビューが入る構造です。 CellViewBinderプロトコルを継承しているカスタムビューだけが、GenericTableViewCellにビューを突っ込めるという仕様ですね。やはりプロトコル優秀すぎる。 実際に使う時 // Viewで使う時(例えばTableのヘッダーなどで) func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return CustomView() } // Cellで使う時 guard let cell = tableView.dequeueReusableCell(withIdentifier: CustomView.identifier) as? GenericTableViewCell<CustomView> else { return UITableViewCell() } // 実際のViewにアクセスするのは、customView経由 let customView = cell.customView 参考文献 GenericなUITableViewCellを使う https://medium.com/eureka-engineering/thought-about-creating-view-component-ios-3cb8e525461a
- 投稿日:2021-10-26T22:27:06+09:00
コードでTabBarItemの追加
今回の内容 コードと簡単解説 TodayViewControllerとSearchViewControllerが存在すると仮定して進めます。 まずは、viewControllersに入れる為のUIViewControllerを準備します。 表示したいtabBarItemをUITabBarItem(title:, image:, tag:)で設定します。 viewControllers = [todayViewController,searchViewController]でTabを設定していきます。 let todayViewController = TodayViewController() todayViewController.tabBarItem = UITabBarItem(title: "Today", image: UIImage(systemName: "clock.fill"), tag: 0) let searchViewController = SearchViewController() searchViewController.tabBarItem = UITabBarItem(title: "Search", image: UIImage(systemName: "magnifyingglass"), tag: 1) viewControllers = [todayViewController,searchViewController] 終わり ご指摘、ご質問などありましたら、コメントまでお願い致します。
- 投稿日:2021-10-26T21:12:40+09:00
【Swift】Macでメディアキーの押下をエミュレートする
メディアキーとは ここでメディアキーと呼んでいるのは、キーボードの最上段にある↓このキーのことです。 メディアキーをプログラムから入力するコードは以下の通り。 メディアキー入力 func HIDPostMediaKey(_ key: Int32) { func doMediaKey(_ key: Int, down: Bool) { let modifierFlags = NSEvent.ModifierFlags(rawValue: down ? 0xA00 : 0xB00) let nsEvent = NSEvent.otherEvent(with: .systemDefined, location: NSPoint(x: 0, y: 0), modifierFlags: modifierFlags, timestamp: 0, windowNumber: 0, context: nil, subtype: 8, data1: (key << 16) | ((down ? 0xA : 0xB) << 8), data2: -1) let cgEvent = nsEvent?.cgEvent cgEvent?.post(tap: .cghidEventTap) } doMediaKey(Int(key), down: true) //key press doMediaKey(Int(key), down: false) //key release } 使い方は簡単で、引数にキータイプを指定するだけ。例えば、音量アップならNX_KEYTYPE_SOUND_UPを指定する。 音量アップ HIDPostMediaKey(NX_KEYTYPE_SOUND_UP) 引数で指定するキータイプは、下記の通り。 キー キータイプ定数 NX_KEYTYPE_BRIGHTNESS_DOWN NX_KEYTYPE_BRIGHTNESS_UP NX_KEYTYPE_REWIND NX_KEYTYPE_PLAY NX_KEYTYPE_FAST NX_KEYTYPE_MUTE NX_KEYTYPE_SOUND_DOWN NX_KEYTYPE_SOUND_UP NX_KEYTYPE_EJECT 上のものも含めて、キータイプは下記の通り記述されている。いくつか試したが、機能しているかどうか確認できなかったものが多かった。 IOKit.hidsystem public var NX_NOSPECIALKEY: Int32 { get } public var NX_KEYTYPE_SOUND_UP: Int32 { get } public var NX_KEYTYPE_SOUND_DOWN: Int32 { get } public var NX_KEYTYPE_BRIGHTNESS_UP: Int32 { get } public var NX_KEYTYPE_BRIGHTNESS_DOWN: Int32 { get } public var NX_KEYTYPE_CAPS_LOCK: Int32 { get } public var NX_KEYTYPE_HELP: Int32 { get } public var NX_POWER_KEY: Int32 { get } public var NX_KEYTYPE_MUTE: Int32 { get } public var NX_UP_ARROW_KEY: Int32 { get } public var NX_DOWN_ARROW_KEY: Int32 { get } public var NX_KEYTYPE_NUM_LOCK: Int32 { get } public var NX_KEYTYPE_CONTRAST_UP: Int32 { get } public var NX_KEYTYPE_CONTRAST_DOWN: Int32 { get } public var NX_KEYTYPE_LAUNCH_PANEL: Int32 { get } public var NX_KEYTYPE_EJECT: Int32 { get } public var NX_KEYTYPE_VIDMIRROR: Int32 { get } public var NX_KEYTYPE_PLAY: Int32 { get } public var NX_KEYTYPE_NEXT: Int32 { get } public var NX_KEYTYPE_PREVIOUS: Int32 { get } public var NX_KEYTYPE_FAST: Int32 { get } public var NX_KEYTYPE_REWIND: Int32 { get } public var NX_KEYTYPE_ILLUMINATION_UP: Int32 { get } public var NX_KEYTYPE_ILLUMINATION_DOWN: Int32 { get } public var NX_KEYTYPE_ILLUMINATION_TOGGLE: Int32 { get } このファイルの定数をSwiftで宣言しただけと思われる。(NeXTが懐かしい) なお、上記のコードをはじめて実行すると、システム環境設定のプライバシーが開くので、該当appにアクセシビリティの制御許可を与える必要があります。 インテルMacbook Pro / macOS 11.6 / Xcode 13.1 / Swift 5.5.1 で動作確認済み 参考 以下のPythonコードを参考にした。 キーボードとマウスのエミュレート方法は↓この記事にあります。
- 投稿日:2021-10-26T16:11:36+09:00
Swift JSON+Codableで不正なURL表現の文字列があった場合のデコードエラーを防ぐ
URLのCodable JSONなどのデータをSwiftのStructに落とし込む時、Codableが使われます。 structの各プロパティをCodableに対応したクラスにすることで、JSONをstructに変換することができます。 Codableに対応したクラスですが、Appleのドキュメントに、Encoding and Decoding Custom Types というものがあります。 ここには、String, Int, Double, Date, Data, そしてURLが対応していると列挙されています。 なので、struct中にURLがあっても、その文字列がURLで表現される場合、正しくURLとして変換されます。 以下はURLを含む文字列の例です。 JSONデータ let data = """ { "id": 1234, "name": "Hello World!", "url": "http://example.com" } """.data(using: .utf8) struct MyData: Codable { let id: Int let name: String let url: URL } let ret = try! JSONDecoder().decode(MyData.self, from: data!) print(ret) //MyData(id: 1234, name: "Hello World!", url: http://example.com) 問題 しかし、この文字列→URL変換処理は、URL(string:)で変換できることが条件となっているので、URLの部分の文字列が空文字や日本語を含むURL、またはURLとは看做さない文字列がJSONに含まれていると、JSONそのものがDecodingErrorになってしまいます。 この挙動はstructのURLがURL?とOptionalで定義してあっても同様です。 JSONデータ let data = """ { "id": 1234, "name": "Hello World!", "url": "" } """.data(using: .utf8) let ret = try! JSONDecoder().decode(MyData.self, from: data!) // Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "url", intValue: nil)], debugDescription: "Invalid URL string.", underlyingError: nil)) 対処 この問題を対処するために、一度Codableに対応した別クラス SafeCodableUrl を挟んでデコードを行うようにしてみます。 URLにできる文字列がJSONに入っていた場合、SafeCodableUrlのvalueにURLが存在し、URLにできない文字列の場合、valueはnilとなり、より安全にJSONが扱えるようになります。 struct SafeCodableUrl: Codable { let value: URL? init(from decoder: Decoder) throws { let container = try? decoder.singleValueContainer() if let string = try? container?.decode(String.self) { self.value = URL(string: string) } else { self.value = nil } } } JSONデータ let data = """ { "id": 1234, "name": "Hello World!", "url": "" } """.data(using: .utf8) struct MyData: Codable { let id: Int let name: String let url: SafeCodableUrl //文字列がURLではない場合もデコードエラーにならない } let ret = try! JSONDecoder().decode(MyData.self, from: data!) print(ret.url.value) //nil
- 投稿日:2021-10-26T14:39:52+09:00
AtCoder Beginner Contest 158の[C - Tax Increas]
SwiftでAtCoder Beginner Contest 158の[C - Tax Increas]を解きました。 問題内容 問題文 消費税率が 8 %のとき A 円、10 %のとき B 円の消費税が課されるような商品の税抜き価格を求めてください。 ただし、税抜き価格は正の整数でなければならないものとし、消費税の計算において小数点以下は切り捨てて計算するものとします。 条件を満たす税抜き価格が複数存在する場合は最も小さい金額を出力してください。また、条件を満たす税抜き価格が存在しない場合は -1 と出力してください。 解答コード import Foundation let aa = readLine()!.split(separator: " ").map{ Double($0)! } var array : [Int] = [] let minA = Int(ceil(aa[0] / 0.08)) let maxA = Int(floor((aa[0] + 1) / 0.08)) let minB = Int(ceil(aa[1] / 0.1)) let maxB = Int(floor((aa[1] + 1) / 0.1)) for i in minA...maxA - 1{ array.append(i) } for i in minB...maxB - 1{ array.append(i) } var set = Set<Int>() let repeated = NSOrderedSet(array: array.filter { !set.insert($0).inserted }).array print(repeated.first ?? -1) 配列の重複部分だけ取り出す際コードは以下のQAを参考にしました。
- 投稿日:2021-10-26T12:42:07+09:00
【Swift】UIAlertViewControllerの実装
はじめに ユーザーに確認を求めたりエラー表示をしたりとよくアラートを出したい場面があると思います。 そんな時に毎回アラートを出すコードを書くのは効率が悪いので、どこでもすぐ呼び出せるようにアラート処理用のファイルを作成しておきます。 コピーして使用できるので特に初学者の方のお役に立てればと思います。 アラートを実装 import UIKit final class Alert { // OKアラート static func okAlert(vc: UIViewController, title: String, message: String, handler: ((UIAlertAction) -> Void)? = nil) { let okAlertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) okAlertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: handler)) vc.present(okAlertVC, animated: true, completion: nil) } // OK&キャンセルアラート static func cancelAlert(vc: UIViewController, title: String, message: String, handler: ((UIAlertAction) -> Void)? = nil) { let cancelAlertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) cancelAlertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: handler)) cancelAlertVC.addAction(UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)) vc.present(cancelAlertVC, animated: true, completion: nil) } // TextField付きアラート static func textFieldAlert(vc: UIViewController, title: String, message: String, placeholder: String, securyText: Bool, handler: ((String?) -> Void)? = nil) { let textFieldAlertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) textFieldAlertVC.addTextField { (textField) in textField.placeholder = placeholder textField.isSecureTextEntry = securyText } textFieldAlertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in handler?(textFieldAlertVC.textFields?.first?.text) })) textFieldAlertVC.addAction(UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)) vc.present(textFieldAlertVC, animated: true, completion: nil) } // 自動で消えるアラート static func autoCloseAlert(vc: UIViewController, title: String, message: String) { let autoCloseAlertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) vc.present(autoCloseAlertVC, animated: true) { // 2秒後に消える DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { autoCloseAlertVC.dismiss(animated: true, completion: nil) } } } } 呼び出しコード // OKアラートを表示 Alert.okAlert(vc: self, title: "タイトル", message: "任意のメッセージ", handler: { (_) in // OKボタンを押した後の処理を書いて下さい。 }) // OK&キャンセルアラートを表示 Alert.cancelAlert(vc: self, title: "タイトル", message: "任意のメッセージ", handler: { (_) in // OKボタンを押した後の処理を書いて下さい。 }) // TextField付きアラートを表示 Alert.textFieldAlert(vc: self, title: "タイトル", message: "任意のメッセージ", placeholder: "任意の文字", securyText: false, handler: { (text) in // TextFieldに入力した文字の処理を書いて下さい。 }) // 自動で消えるアラートを表示 Alert.autoCloseAlert(vc: self, title: "タイトル", message: "任意のメッセージ")
- 投稿日:2021-10-26T11:13:23+09:00
Human InterFaceGuidelineを理解する その3
Naviagation ナビゲーションはモーダルと違ってユーザーに画面遷移を気づかせないことがベスト。アプリの目的や用途に自然と溶け込むようにするのが良いとされている。 決して、ユーザーがアプリを触る集中を奪ってはいけない。 navigationの画面遷移を作成するにも三種類の組み方があるらしい。 階層ナビゲーション 一つの画面から階層に潜るようにナビゲーションをする方法。ある一つのコンテンツ、目的に際して、多くの画面分岐をする際に必要そう。 平面ナビゲーション 図にある通り、複数のコンテンツを平面的に並べる時に使う。コンテンツを並べる系アプリでよく使う・ コンテンツやUxドリブンのナビゲーション ゲームなどで使われる。構造上、コンテンツの中身によってどのようにnavigationをするか決める。 ・ユーザーが自身で今どこの画面にいるのか、コンテンツにいるのかを明確にする。 ・コンテンツに簡単にアクセスできるような最小限の情報構造にする ・タッチジェスチャを使用して、簡単に画面遷移をするとよい ・標準のナビゲーションコンポーネントを使用しましょう ・基本はUIKitのコンポーネントにユーザーは慣れているのでそれを利用した画面遷移が推奨される。 ・ナビゲーションバーを使用して、階層を移動させることを明示する。タイトルに書くとわかりやすい ・タブバーを使用して、コンテンツまたは機能の同列なカテゴリを綺麗にまとめる。 タブバーを使用すると、アプリ内での現在の位置に関係なく、簡単に画面を切り替えられる。 ・同タイプコンテンツのページが複数ある場合は、ページコントロールでスワイプして切り替えれるようにする ・ページコントロールは、使用可能なページ数と現在アクティブなページを下の方に明確に伝える必要がある。 オンボーディング インストール後の流れのこと。リピートを増やしたり、最初の流れを教育したりする ・起動画面をつくる。 ・適切な向きで起動しましょう(縦向きか横向きか判断して描画する) ・すぐにアプリ使用に移りましょう(チュートリアルはリピーターには表示しない、最初の画面でのメニュー表示は最小限でコンテンツに集中させるようにする。) ・チュートリアルの基本事項を順守しましょう(アプリ操作は直感的にして、チュートリアルは必要最小限にする) ・学習は楽しく、発見可能に 動かしながら学ぶことは、説明書を読みながらよりもはるかに楽しく、ユーザーに対して効果的です。 アニメーションとインタラクティビティを使用し、コンテキストに沿って徐々に身に付けさせましょう。 また、インタラクティブに見えるスクリーンショットは表示しないでください。 ・表立ってデバイスのセットアップ情報を尋ねることは避ける(多くのセットアップを求めると離脱率が多い。) ・アプリ内でライセンス契約および免責事項を表示しない ・アプリをダウンロードする前、App Storeで同意書と免責事項をしっかり提示する ・アプリの再起動時に以前の状態を復元しましょう ⇨データが消えることがないようにする ・アプリ評価を早期にまた、頻繁に求めすぎないこと⇨何回も評価を求めても嫌がれれるだけだよ ・再起動をユーザーに求めないこと