- 投稿日:2020-06-25T20:07:41+09:00
iOS MultipeerConnectivity Frameworkに必要なデレゲートメソッドたち 【Swift】
はじめに
MCBrowserViewControllerを使わないパターンでのMultipeerConnectivity適用に必要なデレゲートメソッドたちです。
- iOS 13.5
- Swift 5.2
準備編
class xxxxxController { let displayName: String! let serviceType: String! var peerID: MCPeerID! var session: MCSession! var browser: MCNearbyServiceBrowser! var advertiser: MCNearbyServiceAdvertiser! func setup() { displayName = "displayName" serviceType = "serviceType" peerID = MCPeerID(displayName: displayName) session = MCSession(peer: peerID) session.delegate = self browser = MCNearbyServiceBrowser(peer: peerID, serviceType: serviceType) browser.delegate = self browser.startBrowsingForPeers() advertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: serviceType) advertiser.delegate = self advertiser.startAdvertisingPeer() } }SessionDelegate編
extension xxxxxController: MCSessionDelegate { func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { } func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { } func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { } func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { } func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { } }sendメソッドたちfunc send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode) throws func startStream(withName streamName: String, toPeer peerID: MCPeerID) throws -> OutputStream func sendResource(at resourceURL: URL, withName resourceName: String, toPeer peerID: MCPeerID, withCompletionHandler completionHandler: ((Error?) -> Void)? = nil) -> Progress?MCNearbyServiceBrowserDelegate編
extension xxxxxController: MCNearbyServiceBrowserDelegate { func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { } func browser(_ browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: Error) { } func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { browser.invitePeer(peerID, to: session, withContext: nil, timeout: 0) } }MCNearbyServiceAdvertiserDelegate編
extension xxxxxController: MCNearbyServiceAdvertiserDelegate { func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) { invitationHandler(true, session) invitationHandler(false, nil) } func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didNotStartAdvertisingPeer error: Error) { } }以上
- 投稿日:2020-06-25T19:10:02+09:00
CreateMLでスポーツビデオのAction Classifierをつくる(書きかけ)
1、データセットをつくる。
動画に要求されること。
・各パターンごとに60動画(train50,test5,validation5)ぐらい必要。
・画面の中心に一人の全身がおさまっている。
・1クリップに1動作。前後の余分な動きはカットで収録されている。
・動画のフレームレート(1秒間に何コマか)は統一すべき。
・休憩や関係ない運動など、分類したい動作以外の「他の動作」動画も1分類として用意。分類したい動作ごとにフォルダに入れ、フォルダの名前を動作名にする。
(このフォルダの名前が、分類結果の名前として出てくる)。
2、CreateMLでAction Clasifierモデルをトレーニングする。
XcodeのDeveloper ToolsからCreateMLをひらく。
- 投稿日:2020-06-25T17:00:16+09:00
NSAttributedStringをかんたんに生成する拡張関数(Swift)
はじめに
iOSアプリ開発では、複数行に渡る文章の行間(
lineSpacing
)など、文字列に複雑な装飾を施す場合、NSAttributedString
を生成する必要があります。
生成方法が複雑なので、引数を渡してかんたんに生成する拡張関数を実装しました。環境
- OS:macOS Mojave 10.14.6
- Xcode:11.3.1 (11C504)
- Swift:5.1.3
実装
イニシャライザを拡張関数として定義します。
NSAttributedString+Init.swiftimport UIKit extension NSAttributedString { convenience init(font: UIFont, color: UIColor, lineSpacing: CGFloat, alignment: NSTextAlignment, string: String) { var attributes: [NSAttributedString.Key: Any] = [ .font: font, .foregroundColor: color ] let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = lineSpacing paragraphStyle.alignment = alignment attributes.updateValue(paragraphStyle, forKey: .paragraphStyle) self.init(string: string, attributes: attributes) } }使い方
拡張関数として定義したイニシャライザを呼び出すのみです。
after@IBOutlet private weak var descriptionLabel: UILabel! { willSet { newValue.attributedText = NSAttributedString( font: .systemFont(ofSize: 16.0), color: .blue, lineSpacing: 6.0, alignment: .left, string: "description" ) } }拡張関数の定義前と比べると、スッキリ書けることがわかります。
before@IBOutlet private weak var descriptionLabel: UILabel! { willSet { var attributes: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 16.0), .foregroundColor: UIColor.blue ] let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = 6.0 paragraphStyle.alignment = .left attributes.updateValue(paragraphStyle, forKey: .paragraphStyle) newValue.attributedText = NSAttributedString(string: "description", attributes: attributes) } }おわりに
今回紹介した拡張関数は、私が必要としている最低限の引数のみ追加しているので、必要に応じてカスタマイズしてください。
NSMutableAttributedString
として可変に扱う場合にもカスタマイズが必要です。おまけ:他の方法
Twitterで他の人がどう
NSAttributedString
を生成しているか伺ったので、紹介します。
NSAttributedString
を定数化するよくアプリ内で使うAttributeを定数化ですね...
— 大渕雄生@未踏2020! (@obuchi_yuki) June 25, 2020
enum Attributeという名前空間作ってその中に static で書いていっちゃう感じです。行間の大きさが画面間で変わることも少ないので、
NSAttributedString
ごと定数化するのはありだと思いました。
定数化の実装時に私が紹介した拡張関数を使うと、よりスッキリ書けると思います。
String
から生成できるようにする自分は極力楽したくて
— もちゅる|【転職活動中】フルリモートパパエンジニア (@mothule) June 25, 2020
"文字列".decode([DecoratableType])
で書けるlibを書いた https://t.co/z3gngLRzcJ
String
にNSAttributedString
を返す拡張関数を追加する方法です。
こちらもわかりやすくていいと思いました。
ただ文字列をローカライズしている場合、若干読みづらくなるかもしれません。
- 投稿日:2020-06-25T16:48:59+09:00
【Swift5】MapKitまとめ
MapKitに関する最新の情報が少ないように感じたので
よく使うような基本的な使い方についてまとめました!今回の実装コードはGithubにあげているので自由にどうぞ!
Delegate部分はextentionで実装しているので多少異なります。
https://github.com/tomoki-inoue1221/mapkit-sample網羅している内容
基本編
- MapKitを使用するための設定と注意点
- 現在地の表示
- 現在地への照準を合わせる
- 指定した場所にピンを立てる(タイトルやサブタイトルの設定)
- ピンがタップされた時の処理
- ロングタップでタップした場所にピンを立てる
応用編
- 住所から緯度・経度を取得する(ジオコーディング)
- 緯度・経度から住所を取得する(逆ジオコーディング)
- カスタムピンの表示
基本編の実装
MapKit使用するための事前知識
現在地を表示する設定
現在地を表示するためには2点設定が必要です。
- Info.plistに許可設定用の内容を記述
現在地を表示する注意点
現在地を表示するためには実機での確認が必要です。
LocationManagerについて
Mapを使う上ではLocationManagerの存在は避けて通れないので、
こちらを一読しておくと良いです!
【CoreLocation】位置情報を取得するよく使うDelegateメソッドの紹介
- CLLocationManagerDelegate編
sample.swift/// delegateをセットする呼ばれる(位置情報の許可設定に使用) func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {} /// 自分の現在地が更新された時に呼ばれる(現在地更新した時に何か処理したい場合に使用) /// locationsに現在地入ってる func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) {}
- MKMapViewDelegate編
sample.swift/// ピンを追加した時に呼ばれる(ピンを加工したりする) func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {} /// ピンをタップした時に呼ばれる(ピンの詳細情報を出したりする) func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {}現在地の表示
位置情報の許可を求める
- コードの実装
ViewController.swiftimport UIKit // 2つimportする import MapKit import CoreLocation // CLLocationManagerDelegateを継承する class ViewController: UIViewController, CLLocationManagerDelegate { // storyboardから接続する @IBOutlet weak var mapView: MKMapView! // locationManagerを宣言する var locationManager: CLLocationManager! override func viewDidLoad() { super.viewDidLoad() // ロケーションマネージャーのセットアップ locationManager = CLLocationManager() locationManager.delegate = self locationManager!.requestWhenInUseAuthorization() } // 許可を求めるためのdelegateメソッド func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { switch status { // 許可されてない場合 case .notDetermined: // 許可を求める manager.requestWhenInUseAuthorization() // 拒否されてる場合 case .restricted, .denied: // 何もしない break // 許可されている場合 case .authorizedAlways, .authorizedWhenInUse: // 現在地の取得を開始 manager.startUpdatingLocation() break default: break } } }こちらで、Info.plist・storyboardの設定がうまくできていれば
許可を求めるダイアログが表示されます。
現在地に照準を合わせる
- コードの実装
ViewController.swiftimport UIKit import MapKit import CoreLocation class ViewController: UIViewController, CLLocationManagerDelegate { @IBOutlet weak var mapView: MKMapView! var locationManager: CLLocationManager! override func viewDidLoad() { super.viewDidLoad() // ロケーションマネージャーのセットアップ // 省略 // 現在地に照準を合わす // 0.01が距離の倍率 let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) // mapView.userLocation.coordinateで現在地の情報が取得できる let region = MKCoordinateRegion(center: mapView.userLocation.coordinate, span: span) // ここで照準を合わせている mapView.region = region } }初期の照準位置を変更したい場合は、
mapView.userLocation.coordinate
を変更すればよくて、
例えば東京駅に合わせたければ、
東京駅の緯度・経度を調べてViewController.swift// 省略 override func viewDidLoad() { super.viewDidLoad() // ロケーションマネージャーのセットアップ // 省略 // 東京駅に照準を合わす let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) // 東京駅の位置情報をセット let tokyoStation = CLLocationCoordinate2DMake(35.681236, 139.767125) // centerに東京駅のlocationDataをセット let region = MKCoordinateRegion(center: tokyoStation, span: span) mapView.region = region }このように書くと東京駅に照準がアイマス。
指定した場所にピンを立てる
- コードの実装
ViewController.swiftoverride func viewDidLoad() { super.viewDidLoad() // ロケーションマネージャーのセットアップ // 現在地に照準を合わす // 指定値にピンを立てる // ピンを立てたい緯度・経度をセット // let coordinate = CLLocationCoordinate2DMake(35.45, 139.56) // 今回は現在地とする let coordinate = mapView.userLocation.coordinate // ピンを生成 let pin = MKPointAnnotation() // ピンのタイトル・サブタイトルをセット pin.title = "タイトル" pin.subtitle = "サブタイトル" // ピンに一番上で作った位置情報をセット pin.coordinate = coordinate // mapにピンを表示する mapView.addAnnotation(pin) {ピンがタップされた時の処理
ViewController.swift// MKMapViewDelegateを継承 class ViewController: UIViewController,CLLocationManagerDelegate,MKMapViewDelegate { @IBOutlet weak var mapView: MKMapView! var locationManager: CLLocationManager! override func viewDidLoad() { super.viewDidLoad() // 省略 // delegateをセット mapView.delegate = self } func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { // タップされたピンの位置情報 print(view.annotation?.coordinate) // タップされたピンのタイトルとサブタイトル print(view.annotation?.title) print(view.annotation?.subtitle) } }よくあるのはタップした時にモダール的な感じで、ピンの詳細がでてくるみたいな画面
GoogleMapで言うとこの井の頭自然文化公園の詳細情報みたいな感じを出すことが多いかも
ロングタップでタップした場所にピンを立てる(位置情報も取得)
- コードの実装
ViewController.swiftoverride func viewDidLoad() { super.viewDidLoad() // 省略 // ロングタップを検知 let longPress = UILongPressGestureRecognizer(target: self, action: #selector(recognizeLongPress(sender:))) //MapViewにリスナーを登録 self.mapView.addGestureRecognizer(longPress) } //ロングタップした時に呼ばれる関数 @objc func recognizeLongPress(sender: UILongPressGestureRecognizer) { //長押し感知は最初の1回のみ if sender.state != UIGestureRecognizer.State.began { return } // 位置情報を取得 let location = sender.location(in: self.mapView) let coordinate = self.mapView.convert(location, toCoordinateFrom: self.mapView) // 出力 print(coordinate.latitude) print(coordinate.longitude) // タップした位置に照準を合わせる処理 let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) let region = MKCoordinateRegion(center: coordinate, span: span) self.mapView.region = region // ピンを生成 let pin = MKPointAnnotation() pin.title = "タイトル" pin.subtitle = "サブタイトル" // タップした位置情報に位置にピンを追加 pin.coordinate = coordinate self.mapView.addAnnotation(pin) }ここで応用編でやる、逆ジオコーディングを使うと住所も取得できる。
応用編の実装
住所から緯度・経度を取得する(ジオコーディング)
よく使うのは住所検索した時に、検索位置に移動してピン立てるみたいな動き
※今回は簡略化のため、入力された想定で固定の住所から緯度・経度を取得するViewController.swiftclass ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } // 画面に適当にボタンを配置する @IBAction func tap(_ sender: Any) { geoCording() } // ジオコーディング(住所から緯度・経度) func geoCording() { // 検索で入力した値を代入(今回は固定で東京駅) let address = "東京都千代田区丸の内1丁目" var resultlat: CLLocationDegrees! var resultlng: CLLocationDegrees! // 住所から位置情報に変換 CLGeocoder().geocodeAddressString(address) { placemarks, error in if let lat = placemarks?.first?.location?.coordinate.latitude { // 問題なく変換できたら代入 print("緯度 : \(lat)") resultlat = lat } if let lng = placemarks?.first?.location?.coordinate.longitude { // 問題なく変換できたら代入 print("経度 : \(lng)") resultlng = lng } // 値が入ってれば if (resultlng != nil && resultlat != nil) { // 位置情報データを作成 let cordinate = CLLocationCoordinate2DMake(resultlat, resultlng) let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) // 照準を合わせる let region = MKCoordinateRegion(center: cordinate, span: span) self.mapView.region = region // 同時に取得した位置にピンを立てる let pin = MKPointAnnotation() pin.title = "タイトル" pin.subtitle = "サブタイトル" pin.coordinate = cordinate self.mapView.addAnnotation(pin) } } } }これでボタンをタップした時に東京駅に照準があってピンが立つ
緯度・経度から住所を取得する(逆ジオコーディング)
よく使われるのは、基本編でやったロングタップした時に
そこの住所を出すみたいな動きViewController.swiftclass ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } // 画面に適当にボタンを配置する @IBAction func tap(_ sender: Any) { reverseGeoCording() } // 逆ジオコーデインング func reverseGeoCording() { // 住所を取得したい位置情報を宣言(今回は東京駅にセット) let location = CLLocation(latitude: 35.681236, longitude: 139.767125) // 位置情報から住所を取得 CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in guard let placemark = placemarks?.first, error == nil else { return } // 市区町村より下の階層が出力 print(placemark.name!) // 都道府県 print(placemark.administrativeArea!) // なんとか郡とかがあれば(ない場合もあるのでnull回避) print(placemark.subAdministrativeArea ?? "") // 市区町村 print(placemark.locality!) // これで日本語の住所はいい感じにでる print(placemark.administrativeArea! + placemark.locality! + placemark.name!) } } }placemarkにはいろんな値が入ってるので、
リファレンスはこちらの記事がわかりやすかったので参考に!
[iOS] MapKitを使って”ジオコーディング・逆ジオコーディング”をやってみるカスタムピンの表示
よくある自分のオリジナルのピンを表示したりする感じです。
APIなどで地図に表示するデータを取得して表示する、みたいな時に使う感じになると思います。ViewController.swiftimport UIKit import MapKit import CoreLocation // MKPointAnnotation(要するにピン)を継承したカスタムクラスを作成 class MapAnnotationSetting: MKPointAnnotation { // デフォルトだとピンにはタイトル・サブタイトルしかないので、設定を追加する // 今回は画像だけカスタムにしたいので画像だけ追加 var pinImage: UIImage? } class ViewController: UIViewController,CLLocationManagerDelegate,MKMapViewDelegate { @IBOutlet weak var mapView: MKMapView! var locationManager: CLLocationManager! // とりあえずテストデータで画像・タイトル・サブタイトル・位置情報を用意 let pinImagges: [UIImage?] = [UIImage(named: "inu1"),UIImage(named: "inu2")] let pinTitles: [String] = ["白いい犬","茶色い犬"] let pinSubTiiles: [String] = ["比較的白いです","茶色いのが売りです"] let pinlocations: [CLLocationCoordinate2D] = [CLLocationCoordinate2DMake(35.68, 139.56),CLLocationCoordinate2DMake(35.70, 139.56)] override func viewDidLoad() { super.viewDidLoad() // 省略 // カスタムピンの表示 // for文で配列の値を回す(ここはいろんなやり方があると思います。) for (index,pinTitle) in self.pinTitles.enumerated() { // カスタムで作成したMapAnnotationSettingをセット(これで画像をセットできる) let pin = MapAnnotationSetting() // 用意したデータをセット let coordinate = self.pinlocations[index] pin.title = pinTitle pin.subtitle = self.pinSubTiiles[index] // 画像をセットできる pin.pinImage = pinImagges[index] // ピンを立てる pin.coordinate = coordinate self.mapView.addAnnotation(pin) } } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // 自分の現在地は置き換えない(青いフワフワのマークのままにする) if (annotation is MKUserLocation) { return nil } let identifier = "pin" var annotationView: MKAnnotationView! if annotationView == nil { annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier) } // ピンにセットした画像をつける if let pin = annotation as? MapAnnotationSetting { if let pinImage = pin.pinImage { annotationView.image = pinImage } } annotationView.annotation = annotation // ピンをタップした時の吹き出しの表示 annotationView.canShowCallout = true return annotationView } }Mapを触ってみて
いろんなことができすぎるのと、仕組みがまだ理解できていないのでちゃんと理解しないとしんどそう・・。
次はFirebaseと連携してUber的なアプリ作る
- 投稿日:2020-06-25T14:31:55+09:00
iOS 14で変わるピッカーと、新しく登場したカラーピッカー
WWDC 2020のDesign with iOS pickers, menus and actionsを見たのでメモです。
※ここに出てくるスクリーンショットは、全て上記の動画のものです。
概要
iPhoneやiPadのアプリでiOS 14から使えるようになった3つのコンポーネントについての紹介。
Menu
iOS 13まで
iPhoneはアクションシート、iPadの場合はポップオーバーを使ってメニューを表示していた。最近はiPhoneの画面が大きくなったりiPadの画面が高密度になってきたので、次のような欠点が見えてくるようになってきた。
- 背景が暗くなり、重苦しさがあった
- アクション名が短くても、シートは大きくなってしまう
- アクション内容が制限されている
- ボタンの位置とアクションシートの位置が離れることがあるので指の移動量が多くなることがある
iOS 14 ~
どのボタンからもメニューを表示できるようになった。
ボタンをタップしたらすぐそばにメニューが表示される。複数行にも対応してるし、選択やナビゲーションの用途にも使える。
表示はコンテキストメニューとよく似ている。各アクションの左側にラベルがあり、右側にはアイコンがついている。アイコンはSF Symbolかカスタム画像を入れることができる。タイトルを追加することも、セパレータを追加することもできる。
項目の選択とキャンセル
普通にタップして選択することもできるし、メニューが仕込まれたボタンをタップ、そのまま指を離さず、出てきたメニューの選択したい項目まで指をスライド、指を離してもボタンが選択されたことになる(動画の2:50-3:00くらいを見るといい)。メニューを消すにはメニュー外をタップすれば良い。そのため、キャンセルボタンを用意する必要はない。
特徴
新しいメニューには次のような特徴がある。
曖昧さを解消できる
メニューは選択の曖昧さを解消するのに役立つ。例えば写真アプリでは「+」という追加ボタンがある。これをタップすると具体的に何を追加したいかを尋ねるメニューが出現する。
ナビゲーションにもなる
メニューはナビゲーションにも使える。例えばSafariでは戻るボタンを長押しするとそのセッションで以前訪れたサイトのリストが表示される。
選択ができる
「もっと見る」ボタンもアクションメニューを表示させるうってつけの例。Fileアプリを例に取ると、iOS 13だとアクションがバラバラに配置されていたが、iOS 14だと一つにまとめられる。とはいえ、何でもかんでも「もっと見る」の中に隠すのは推奨してない。メインアクションとサブアクションをしっかり切り分けて考えて配置する必要がある。
セカンダリオプションが表示できる
Safariの場合、普通に右上のボタンをタップするとこうなるが…
長押しすると関連メニューが出てくるようになっている。
破壊的なアクションの扱い
リマインダーアプリで削除を行う場合の例。破壊的なアクションは慎重に確認させるべきものなので、削除の確認アクションはメニューとは別の場所にあるべき。
これは新メニュー内に限らず、他の破壊的なアクションボタンの場合でも同様。
日付ピッカー
iOS 13までの日付、時間ピッカーはこのようなデザインだった。複数のホイールを動かさなければならなかった。
iOS 14からはこのようなデザインになる。
左右にスライドさせるだけで簡単に月を移動できる。年月の部分をタップするとホイールで選択することもできる。
時間は自分で入力することになる。
リマインダーの例のようにピッカーをインラインで表示できれば効果的だが、必ずしもそうできない場合もある。その場合はコンパクトモードを使う。UIKitに日付ピッカーをリクエストするとこのようなボタンが表示される。
ボタンをタップすると日付ピッカーが表示される。ピッカーの外側をタップすると決定になる。
カラーピッカー
iOS 14では新しくカラーピッカーが導入されている。グリッド、スペクトル、RGBから色を選ぶことができる。
左上のピペットを使えば拡大鏡が出てくるので、例えば写真から色を選ぶこともできる。
また、どのアプリからでもアクセスできるパレットに色を保存することもできる。
- 投稿日:2020-06-25T14:04:31+09:00
【Swift】一度だけ実行したい処理の書き方
何回も呼ばれるメソッドで1度だけ行いたい処理の記述方法をまとめました。
やり方はたくさんありますが、これが1番私的には使いやすかったです。1番簡単な方法
class OnceTrackerSample1 { var isFirst = Bool func manyCalledFunc() { if !isFirst { return } // 1回のみ行いたい処理 } }1番最初に全ての人間が思いつきそうな処理。
やっぱりダサいので、使い勝手よくカッコよく書きたい。DispatchQueueを拡張して使いやすくする
DispatchQueue
をextension
します。private static var _onceTracker = [String]() class func once(token: String, block:()->Void) { // 排他制御 objc_sync_enter(self); defer { objc_sync_exit(self) } // token: Stringで一意に実行状況を管理しています if _onceTracker.contains(token) { return } // 実行履歴を残して、block:()->Voidで渡された処理を実行 _onceTracker.append(token) block() }使う側はこんな感じです。
class OnceTrackerSample1 { func manyCalledFunc() { DispatchQueue.once(token: "key name") { // 1回のみ行いたい処理 } } }スマートでいい感じ!
String
のkey
はどこかでまとめて管理したらより使いやすそう。
- 投稿日:2020-06-25T11:10:41+09:00
swiftメモ
コードから直接セーフアリアにconstraintする
swift5import UIKit import WebKit class ViewController: UIViewController, WKNavigationDelegate { override func viewDidLoad() { super.viewDidLoad() let webView = WKWebView(frame: .zero) webView.translatesAutoresizingMaskIntoConstraints = false webView.navigationDelegate = self view.addSubview(webView) webView.leftAnchor.constraint(equalTo: view.safeLeftAnchor).isActive = true webView.rightAnchor.constraint(equalTo: view.safeRightAnchor).isActive = true webView.topAnchor.constraint(equalTo: view.safeTopAnchor, constant: 60).isActive = true webView.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: 100).isActive = true } }配列を値から検索して削除
削除したい値以外で新しい配列を作成
swift5var array = ["a","b","c","d","e","f"] array = array.filter { $0 != "d" } print(array) //結果:["a", "b", "c", "e", "f"]
- 投稿日:2020-06-25T11:07:59+09:00
iOS Add the following keys to your Info.plist file, located in <project root>/ios/Runner/Info.plist:のエラーハンドリング
エラー内容
iOS Add the following keys to your Info.plist file, located in /ios/Runner/Info.plist: NSPhotoLibraryUsageDescription - describe why your app needs permission for the photo library. This is called Privacy - Photo Library Usage Description in the visual editor. NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor.
Flutterアプリケーション内でカメラへのアクセスと写真ライブラリーを試みた時に生じたエラー。アクセスを試みると同時にアプリが落ちてしまう。
Info.plist
ファイルに許可を与えることが必要らしい。変更
画像を見て一番上の二つのpropertyを加える。
key:
Privacy - Camera Usage Description
とPrivacy - Photo Library Usage Description
を加える。型を決め、value:
NSCameraUsageDescription
とNSPhotoLibraryUsageDescription
を加えれば、カメラへのアクセス、写真ライブラリーへのアクセスが可能になる。方法
加え方は簡単でkeyのどれかにカーソルを合わせると+ボタンが出てくるのでそれをタップするとkeyの一覧が自動的に出てくる。
あとは追加したいkey、型、Valueを一覧から選ぶだけ。
参考
日本語情報が全然出てこなかったので書きました。下記のStackoverflowのリンクでより丁寧に方法を教えてくれているので英語で良いという方はこちらの方を参考にしてください。
NSPhotoLibraryUsageDescription key must be present in Info.plist to use camera roll
- 投稿日:2020-06-25T08:49:06+09:00
UIButtonを画像付きのテキストにする
テキストの横に画像のあるボタンを作成したい場面に出会ったことのある人は結構いると思います。
そんな時UIImage
とUILabel
を組み合わせなくてもUIButton
のみで作ることができます。完成はこんな感じです。
UIButtonに画像とテキストを設定する
leftImageButton.setTitle("左側アイコン", for: .normal) leftImageButton.setImage(R.image.ika(), for: .normal)titleEdgeInsets・imageEdgeInsetsを使います
EdgeInsets
は余白の設定ができます。
それぞれ最適に余白の設定をすることで、ボタンとテキストをいい感じに表示させることができます。
初期値はtitleEdgeInsets
もimageEdgeInsets
も(top: 0, left: 0, bottom: 0, right: 0)
なので、画像とテキストを同時に設定すると被って表示されます。テキストの左に画像を付ける
テキストの左側に余白を設定します。画像の
width
を元に設定するのが良いと思います。leftImageButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)テキストの右に画像を付ける
画像を右側に持っていく為に左側にテキストの長さ分の余白を設定します。こちらも固定値を設定していますが、テキストの長さから設定した方が良いと思います。
またテキストの位置も調整したいので、テキストの余白も設定します。rightImageButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 200, bottom: 0, right: 0) rightImageButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 20)仕組みがわかれば同じ要領でテキストの上側に画像を設定したり、下に画像を設定したりできます。
テキストの上に画像を付ける
topImageButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 100, bottom: 20, right: 0) topImageButton.titleEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)テキストの下に画像を付ける
bottomImageButton.imageEdgeInsets = UIEdgeInsets(top: 25, left: 100, bottom: 0, right: 0) bottomImageButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)以上です。
仕組みを理解すれば簡単にちょっと凝ったボタンを作成することができてとても便利です。関連
- 投稿日:2020-06-25T08:49:06+09:00
【Swift】UIButtonを画像付きのテキストにする
テキストの横に画像のあるボタンを作成したい場面に出会ったことのある人は結構いると思います。
そんな時UIImage
とUILabel
を組み合わせなくてもUIButton
のみで作ることができます。
- R.swiftを使っています
- 表示する画像はこちらのイカちゃん
完成はこんな感じです。
UIButtonに画像とテキストを設定する
leftImageButton.setTitle("左側アイコン", for: .normal) leftImageButton.setImage(R.image.ika(), for: .normal)titleEdgeInsets・imageEdgeInsetsを使います
EdgeInsets
は余白の設定ができます。
それぞれ最適に余白の設定をすることで、ボタンとテキストをいい感じに表示させることができます。
初期値はtitleEdgeInsets
もimageEdgeInsets
も(top: 0, left: 0, bottom: 0, right: 0)
なので、画像とテキストを同時に設定すると被って表示されます。テキストの左に画像を付ける
テキストの左側に余白を設定します。画像の
width
を元に設定するのが良いと思います。leftImageButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)テキストの右に画像を付ける
画像を右側に持っていく為に左側にテキストの長さ分の余白を設定します。こちらも固定値を設定していますが、テキストの長さから設定した方が良いと思います。
またテキストの位置も調整したいので、テキストの余白も設定します。rightImageButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 200, bottom: 0, right: 0) rightImageButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 20)仕組みがわかれば同じ要領でテキストの上側に画像を設定したり、下に画像を設定したりできます。
テキストの上に画像を付ける
topImageButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 100, bottom: 20, right: 0) topImageButton.titleEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)テキストの下に画像を付ける
bottomImageButton.imageEdgeInsets = UIEdgeInsets(top: 25, left: 100, bottom: 0, right: 0) bottomImageButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)以上です。
仕組みを理解すれば簡単にちょっと凝ったボタンを作成することができてとても便利です。関連
- 投稿日:2020-06-25T07:40:51+09:00
SwiftUIでナビゲーションバーの中央にロゴ画像を配置する方法
最近SwiftUIを触り始めて、巷でよく見るNavigationBarの中央にロゴ画像を配置する実装をしようとしたところ、少しつまづいたので、備忘録がわりに書きます。
UIKit
まずは、UIKitでナビゲーションバーで中央に画像を配置する場合はUIViewControllerのnavigationItemが持つ
titleView
というプロパティにUIImageViewを設定することで、表示することができます。import UIKit class ViewController: UIViewController { func viewDidLoad() { super.viewDidLoad() self.navigationItem.titleView = UIImageView(image: UIImage(named: "logo")) } }SwiftUI
SwiftUIで同じことをやろうとした場合、NavigationViewのタイトルの文字列を設定する関数は存在するのですが、どうやらUIKitでのtitleViewにあたる、ナビゲーションバーの中央にViewを配置する関数というのは存在しない様です。
import SwiftUI struct SwiftUIView: View { var body: some View { NavigationView { Text("Hello World") .navigationBarTitle("App Name", displayMode: .inline) // 文字列は設定できるが、Imageは設定できない? } } }調べてみるとナビゲーションバー左右にViewを配置する関数を利用して、画像を配置してから、画面のサイズを元に中央に寄せる方法などもあったのですが、元来想定されていなさそうな使い方の様に感じたので、別の方法で試してみました。
それが以下の方法です。
import SwiftUI struct SwiftUIView: View { var body: some View { ZStack(alignment: .top) { NavigationView { Text("Hello, World!") .navigationBarTitle("", displayMode: .inline) } Image(uiImage: UIImage(named: "logo")) .resizable() .scaledToFit() .frame(width: nil, height: 44, alignment: .center) } } }alignmentを.topに設定した
ZStack
(Viewの重なりを設定するStack)に、NavigationViewとImageを配置することで、最前面の上部であるナビゲーションバーの中央にImageを表示する仕組みです。
- 投稿日:2020-06-25T07:39:38+09:00
EmbeddedFrameworkでCarthageを使用した際にPreviewがPotentialCrashError: Update failedになる場合の対処法
Xcode11から使用できる様になったPreview機能はSwiftUIでの開発をサポートしてくれる強力な味方ではあるのですが、特定の状況で読み込めなくなってしまうこともあり、そうなった場合は開発が滞ったりしてしまいます。
今回、EmbeddedFrameworkとCarthageを利用してSwiftUI開発している際に、Previewが読み込めなくなってハマった際の対処法を紹介します。
環境
- Swift5.2
- Xcode11.5
- Carthage
- Nuke(画像キャッシュ)
- Alamofire(API通信)
プロジェクトの構成はアプリのターゲットに、
Domain
、DataStore
、Presentation
の三つのEmbeddedFrameworkを追加しています。
UI周りのコードは
Presentation
に全て書いているので、そのFramework内でPreviewを確認します。PotentialCrashError: Update failed
では、早速PresentationのFramework内に試しに書いたPreviewを見てみましょう。
struct SwiftUIView_Previews: PreviewProvider { static var previews: some View { Text("Hello, World!") } }読み込めない?
エラーの詳細を見てみると・・・
どうやらCarthageで導入しているNukeが読み込めないがためにPreviewが表示されないようです。
しかしシミュレーターのビルドは通っているので、ちゃんとCarthageで導入したFrameworkの参照はうまくいってるはず?
解決法
色々と調べていると、PreviewからFrameworkの参照するためのパスの設定はBuild Settingの
Framework Search Paths
ではなくRunpath Search Paths
から行われているようです。そうと分かれば、以下の画像のように
Runpath Search Path
に、新たにFramework Search Paths
に入力されている$(PROJECT_DIR)/Carthage/Build/iOS
を追加してあげれば、Preview時にCarthageで導入したFrameworkも参照できるようになりそうです。この状態でPreviewを確認すると・・・
ちゃんと表示された!?
最後に
以上がEmbeddedFrameworkにCarthageで導入したFramework際にPreviewが表示されない場合の対処法でした。
この記事によって、誰かが私のようにこの問題を修正するために、EmbeddedFramework化を諦めそうになったり、休日の大事な時間を無駄にしたりしないことを祈ります?
- 投稿日:2020-06-25T01:29:24+09:00
iOSで100vhが効かない問題で、楽な方法をNuxt.jsでやってみる
iOS safariで100vhがうまくいかない時に、
window.innerHeight
を取って各要素のstyle属性に一個一個付ける、
というのをやりがちでしたが、楽な方法が無いかと探すとカスタムプロパティが使えるそうなので、
その手順をNuxt.js版で書きます。mixinを作る
--wh
という名前でカスタムプロパティを定義する。
ロード時と、リサイズ時にwindow.innerHeight
を取って--wh
に入れる。mixins/device.jsexport default { data: () => ({ style: { '--wh': '100vh' } }), mounted() { this.$nextTick(() => { this.getWindowSize() window.addEventListener('resize', this.getWindowSize) }) }, methods: { getWindowSize() { this.style['--wh'] = `${window.innerHeight}px` } } }mixinを読み込む
mixinを読み込んで、styleオブジェクトをwrapper要素のstyle属性に入れる。
layouts/default.vue<template lang="pug"> .l-wrap(:style="style") nuxt </template> <script> import deviceMixin from '~/mixins/device' export default { mixins: [deviceMixin] } </script>カスタムプロパティを使う
heightに
var(--wh, 100vh)
と書いて、使用する。
書き方: var(設定したカスタムプロパティ名, プロパティが無い時用の初期値)pages/index.vue<template lang="pug"> .p-top </template> <script> export default {} </script> <style lang="stylus" scoped> .p-top height var(--wh, 100vh) </style>まとめ
これで、iOSのツールバーを考慮した実装が
- wrapperで
window.innerHeight
取って、変数に格納する- wrapperかそれ以下の小要素で
height var(--wh, 100vh)
するという形でほぼCSSで指定出来るようになりました。
IEはまだ未検証ですが、Nuxt.jsの場合は
height var(--wh, 100vh)
とすると、
height 100vh
を自動的に手前に追加してくれるようなので、
varが認識されなくても問題なくvhできるかと思います。<style lang="stylus" scoped> .p-top height 100vh height var(--wh, 100vh) </style>※mixinを使ったのはlayoutsが複数あるときに、各所に同じ処理を入れることになりそうで面倒だと思ったからなので、default.vueでmixinでやってる処理をそのまま書いても問題ないです。