20210418のiOSに関する記事は8件です。

[Swift] CombineでAnyCancellableを省略する

前置き Combine を使うと必ず書くことになるのが AnyCancellable です。 これを毎回書くのが億劫だなと感じたので、書かずに済む方法を考えました。 前提 RxSwiftでの実装を見る RxSwift を使った際にも DisposeBag を書き続けなければならないという問題がありました。 これにはライブラリがあり、それを導入して解決することができました。 上記、中身はたくさんありますが、実質的には このコード だけで機能しています。 fileprivate var disposeBagContext: UInt8 = 0 extension Reactive where Base: AnyObject { func synchronizedBag<T>( _ action: () -> T) -> T { objc_sync_enter(self.base) let result = action() objc_sync_exit(self.base) return result } } public extension Reactive where Base: AnyObject { var disposeBag: DisposeBag { get { return synchronizedBag { if let disposeObject = objc_getAssociatedObject(base, &disposeBagContext) as? DisposeBag { return disposeObject } let disposeObject = DisposeBag() objc_setAssociatedObject(base, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return disposeObject } } set { synchronizedBag { objc_setAssociatedObject(base, &disposeBagContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } } 馴染みがないとわかりづらいですが、行っていることはとてもシンプルです。 通常はextensionで生やせないメンバ変数を、Objectice-C時代からある黒魔術を使って追加しています。(もっとシンプルな例が見たい方はこちら) 上記のコードを AnyCancellable に適応することで、省略することが可能です。 実装 今回は3つの段階を踏んで紹介しますが、プロジェクトに導入する場合は段階の好み選んでいただければと思います。 1. NSObject を拡張する 多くのクラスで基底部分にあるNSObjectを拡張する形をとります。 private var cancellableContext: UInt8 = 0 extension NSObject { var cancellables: Set<AnyCancellable> { get { if let cancellables = objc_getAssociatedObject(self, &cancellableContext) as? Set<AnyCancellable> { return cancellables } let cancellables = Set<AnyCancellable>() objc_setAssociatedObject(self, &cancellableContext, cancellables, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return cancellables } set { objc_setAssociatedObject(self, &cancellableContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } 前提で記載したRxSwiftの場合は、rx.disposeBagという「rx」プレフィックスをワンクッション挟む実装の都合上、若干長くなっていましたが、それがない分だけシンプルなコードになりました。 example final class Object: NSObject { // これが不要になる // private var cancellables = Set<AnyCancellable>() func exec() { Just<Int>(0) .sink(receiveCompletion: { _ in // do something }, receiveValue: { _ in // do something }) .store(in: &self.cancellables) // 参照箇所 } } このように、クラス内に AnyCancellable を定義しなくても呼び出せるようになります。 2. Publisher を拡張する 先の「1」で行った実装のままでも十分に機能します。 ただ、コードを書いていくうちに AnyCancellable を登録する部分である 「 .store(in: &self.cancellables) 」も書くのが億劫になります。 その場合は、さらにPublisherを拡張して、その部分を隠蔽化することで対応できます。 extension Publisher { // case 1 func sink<T: NSObject>(_ target: T, receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) { sink(receiveCompletion: receiveCompletion, receiveValue: receiveValue).store(in: &target.cancellables) } } extension Publisher where Self.Failure == Never { // case 2 func sink<T: NSObject>(_ target: T, receiveValue: @escaping ((Self.Output) -> Void)) { sink(receiveValue: receiveValue).store(in: &target.cancellables) } // case 3 func assign<O: NSObject>(to keyPath: ReferenceWritableKeyPath<O, Self.Output>, on object: O) { assign(to: keyPath, on: object).store(in: &object.cancellables) } } example final class Object: NSObject { // case 1 func exec1() { Just<Int>(0) .sink(self, receiveCompletion: { _ in // do something }, receiveValue: { _ in // do something }) } // case 2 func exec2() { Just<Int>(0) .sink(self, receiveValue: { _ in // do something }) } // case 3 private var number: Int = 0 func exec3() { Just<Int>(10) .assign(to: \.number, on: self) } } AnyCancellable を登録するコードがない分、だいぶ簡潔になりました。 3. Protocol で制限する 「1」「2」で行った実装では、extension でグローバルに拡張しているため、どこからでも呼び出せてしまいます。そこに一定の制約を設けたい場合は Protocol で縛っていきます。 protocol Storable: NSObject { var cancellables: Set<AnyCancellable> { get set } } この作成したプロトコルを、拡張したメソッドにも適応して縛ります。 旧: where T: NSObject ↓↓↓ 新: where T: Storable つまり、以下のようになります。 extension Publisher { func sink<T: Storable>(_ target: T, receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) { sink(receiveCompletion: receiveCompletion, receiveValue: receiveValue).store(in: &target.cancellables) } } extension Publisher where Self.Failure == Never { func sink<T: Storable>(_ target: T, receiveValue: @escaping ((Self.Output) -> Void)) { sink(receiveValue: receiveValue).store(in: &target.cancellables) } func assign<O: Storable>(to keyPath: ReferenceWritableKeyPath<O, Self.Output>, on object: O) { assign(to: keyPath, on: object).store(in: &object.cancellables) } } example 使用したい NSObject にのみ Storable を適応することで使用することができます。 final class Object: NSObject, Storable { func exec() { Just<Int>(0) .sink(receiveCompletion: { _ in // do something }, receiveValue: { _ in // do something }) } } ライブラリ せっかくなのでライブラリ化しました。 Cocoapods Carthage SPM 3つとも対応しています。 何かあればPRお願いいたします? 終わりに 前置きから長くなってしまいましたが、コードを少しでも簡単に書くためのテクニックくらいに思っていただけると幸いですmm もっとこうした方が良いなどあればコメントいただけると!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift - frameとboundsの違い

はじめに 初回の投稿ですが、お手柔に宜しくお願いします〜 ところで、frameとboundsの違いについて結構最近やっとわかった、記録しておこうかなと思います。 frameとは frameはsuperviewが基準にして相対的な座標・大きさを返すプロパティ boundsとは boundsは要素自身が基準にして相対的な座標・大きさを返すプロパティ コードで理解 sample.swift let yellowView: UIView = UIView() yellowView.backgroundColor = UIColor.yellow yellowView.frame = CGRect(x: 50, y: 50, width: 100, height: 100) self.view.addSubview(yellowView) // Yellow ViewのFrameとBoundsをプリントアウト print("Yellow View Frame: \(yellowView.frame)") // Frameをプリントアウト print("Yellow View Bounds: \(yellowView.bounds)") // Boundsをプリントアウト 結果は以下: boundsは自分自身が基準なので、特に設定してない場合x、y両方0です。 画像で理解 boundsを設定した場合はどうなりますか yellowViewにredViewをaddし、さらにyellowViewのboundsのx、yを(-20,-20)にしてみましょう。 sample2.swift let redView: UIView = UIView() redView.backgroundColor = UIColor.red redView.frame = CGRect(x: 0, y: 0, width: 50, height: 50) yellowView.addSubview(redView) print("Red View Frame: \(redView.frame)") print("Red View Bounds: \(redView.bounds)") 結果はこうなってます: また、yellowViewのboundsのx、yを(-20,-20)にしてみましょう。 sample3.swift yellowView.bounds = CGRect(x: -20, y: -20, width: 100, height: 100) 実行してみたら 原因は、yellowViewの原点座標は(-20,-20)になり、redViewの原点座標は(0,0)のままなので、redViewは右下に移動してしまいました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】テストの環境作成手順 (実機ビルドでテストする方法)

この記事を書いた過程 前回の記事同様に、ほとんどiOS開発をした事がない状態で、iOSアプリのテストのタスクが回ってきたので手順をまとめました。 僕と同じ状態の人の助けになればと思います! 今回は、プッシュ通知機能があるアプリケーションをXcodeで実機でビルドしてテストを実施しました。 (※AWS SNSを利用してプッシュ通知を飛ばします。) 前提 Xcode 12 macOS Big Sur バージョン11.2 Swift 5.1 流れはこんな感じです! 1.テスト用の証明書の発行。 2.プロビジョニングファイルの作成。 3.Xcodeで作成したプロビジョニングファイルを使用するように設定する。 4.プッシュ通知が届くように設定する。 5.実機でビルドする。 やっていく! 1.テスト用の証明書の発行 ・Xcodeで [Preference] → [Account] で 開発用アカウントでログイン。 ・開発者を選択して、[manage certificates]をクリック。 左下の「+」から「Apple Developement」を選択して証明書を作成。 ・Developer Center にログインして、証明書をダウンロードする。 2.プロビジョニングファイルの作成 ・Developer Centerにログイン(1と同じアカウント) ・[Profile] → [iOS App Development]を選択して、[Continue] ・該当のApple IDでを選択して、[Continue] ・1で作成した、証明書を選択して[Continue] ・テストで使用したいデバイスを選択する。(※新しくデバイスを登録したい場合は、上記、テスト用IOSデバイスの登録を参照) ・任意の名前を入力して、[Generate]をクリック。 ・[Profile]で作成したプロビジョニングファイルを[Download]しておく。 3.Xcodeで作成したプロビジョニングファイルを使用するように設定する。 ・[Project] → [Targets] → [Build Setting] → [Signing] → [Code Signing Identity]で「IOS Developer」を選択 ・[Project] → [Targets] → [General] → [Singing (Debug)] の[Provisioning file]から[Download Profile]を選択。2で作成したプロビジョニングファイルを選択する。 4.プッシュ通知が届くように設定する ・[Project] → [target] → [Sigining & Capabilities] を開く ・[+Capabilities]をクリックして「Push Notification」を選択。 ・[Background Modes]を選択。 → [Remote notifications]を選択。 5.実機でビルドする ・プロビジョニングファイルに登録したiphoneをPCに接続。 ・ビルドする機種に接続したiphoneを設定して、[▶︎]ボタン(ビルドボタン)をクリック。 ※テストする上での注意 ・テストでは、開発用のプラットフォームアプリケーションを使用してプッシュ通知を飛ばす必要がある。(AWSコンソールでで確認できる。) ・使用するプラットフォームアプリケーションのプッシュ通知証明書の期限が切れていないかを確認する必要あり → 今回はここでハマりました。バックエンドのロジックが開発用と本番用で同じプラットフォームアプリケーションを使用するロジックになっていた為、プッシュ通知が飛ばなくて悩みました。。。 参考 ○テスト用IOSデバイスの登録 https://support.smartbear.com/testcomplete/docs/app-testing/mobile/ios/preparing/registering-devices.html ○プロビジョニングファイルについて https://qiita.com/edo_m18/items/6f10e57f95b25d9dab4e https://qiita.com/fujisan3/items/d037e3c40a0acc46f618 https://zenn.dev/mhackit/scraps/355fe56dc7b4c8 ○Bundle IDについて https://qiita.com/miyamotok0105/items/f24a4e10a2877ae70bc4 ○色々な用語の説明 https://qiita.com/toto_kit/items/df84cd687919ec28591b
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

window.rootViewController入れ替え時のアニメーション

重要な点は、UIView.transitionのanimationsでViewControllerの入れ替えをするのではなく、先に入れ替え処理を書いてからUIView.transitionを呼び出すこと。(animations引数は空のクロージャ。) class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? ... func setRootViewController(_ vc: UIViewController) { guard let window = window else { return } window.rootViewController = vc UIView.transition( with: window, duration: 0.5, options: .transitionFlipFromLeft, animations: {}, completion: { _ in }) } } こんな感じでアニメーションさせられます。 アニメーションの種類は他にもあるので .transitionFlipFromLeft に定義ジャンプして確認してみてください。 ページ送りやフェードのアニメーションもできます。 参考 Swap rootViewController with animation?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift]虹色のボーダーアニメーションを作成する

はじめに 今回は使い所は限られるとは思いますが、虹色ボーダーラインのアニメーションを実装します。 こんな感じ↓ Swiftで虹色のボーダーラインのアニメーション pic.twitter.com/tzrhSnnebS— M (@p_x9) April 18, 2021 本編 1. 虹色のCGColor配列を作成 虹色の配列を作成します。色相、彩度、明度のうち、色相を少しずつ変化させればなだらかな虹色が作成できます。 後々のためここではUIColorではなくCGColorの配列としておきます。 var rainbow: [CGColor] { let increment: CGFloat = 0.02 return [CGFloat](stride(from: 0.0, to: 1.0, by: increment)).map{ hue in UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0).cgColor } } 2. 虹色のグラデーションを作成する まず虹色のグラデーションのレイヤを作成します。 グラデーションのレイヤを作成するにはCAGradientLayerを使います。 ここでCAGradientLayerのcolorsプロパティに先ほど作成した虹色の配列を適用することで、虹色グラデーションのレイヤができます。 let gradientLayer = CAGradientLayer() gradientLayer.colors = rainbow 今回はラベルに虹色のボーダーラインアニメーションを追加しますが、そのフレームに対し少しマージンを取ります。 gradientLayer.frame = CGRect(x: -4, y: -4, width: label.frame.width+8, height: label.frame.height+8) グラデーションの始まりと終わりのポイントを指定します。 今回は左上から右下にかけてのグラデーションにしたいので、以下のように設定しました。この時、x,yともに0から1の範囲で指定する必要があるので注意が必要です。 gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0) gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) こんな感じです。 3. アニメーションを行うレイヤの作成 CAShapeLayerを使用します。 まずボーダラインのpathを指定します。 let shapeLayer = CAShapeLayer() let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: label.frame.width+8, height: label.frame.height+8)) shapeLayer.path = path.cgPath またfillColorは透明にしておきます。線の太さも決めておきます。 shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineWidth = 8.0 4. maskの設定 グラデーションのレイヤにmaskとして3で作成したアニメーションを行うレイヤを設定します。 四角いグラデーションのレイヤから、maskに設定したレイヤによってくり抜くといったイメージです。 5. レイヤをviewに適用 今回はlabelに対して設定します。 label.layer.addSublayer(shapeLayer) アニメーションの作成 CABasicAnimationを使用します。CAShapeLayerのstrokeEndプロパティを0から1までアニメーションさせたいので以下のように実装します。 また、アニメーションの時間も設定しておきます。 let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.strokeEnd)) animation.fromValue = 0.0 animation.toValue = 1.0 animation.duration = 3.0 ここではアニメーションを無限にループさせたいので以下のように設定しておきます。 animation.repeatDuration = .infinity 6. animationのレイヤへの適用 以下を実行するとアニメーションが始まります。 self.shapeLayer.add(animation, forKey: #keyPath(CAShapeLayer.strokeEnd)) 7.完成 これで完成です。 動作原理 まず、CAGradientLayerでグラデーションのレイヤを作ります。 maskプロパティに別レイヤを設定すると、設定したレイヤの形にくり抜くことができます。 そこで、CAShapeLayerで外枠となる部分のレイヤを作成し、maskとして適用することでグラデーションの外枠ができます。 CAShapeLayerのstrokeStartとstrokeEndが外枠の始まりと終わりの部分を表すのでこと値をアニメーションで徐々に変化させることで、外枠がアニメーションし目的を達成することができます。 おわりに 読んでいただいてありがとうございます。初めて二日連続で記事を書きました。参考になれれば嬉しいです。 ご指摘ご質問等ございましたら、コメントまたはDMまでお願いします。 本記事での内容はこちらのリポジトリよりダウンロードできます。 簡単に実行できるので良かったら試してみてください 追記 2021/04/19 この記事に関連した記事を書きました。 合わせて読んでいただけると嬉しいです https://qiita.com/p_x9/items/5fbe76748ee060f18daa
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift5] cellをtapしたときのhighlightを無効にする方法

はじめに tableView全てのcell, または,特定のcellをtapしたときのhighlightを無効にする方法をメモしました. tableView全体のcellに適用する場合 tableView.allowsSelection = false 上記を記述することで,didSelectRowAt#が動作しなくなるので,tapしてもアニメーションが発生しなくなります. ただし,cellの上にUIButtonを配置した場合,そのbuttonはdidSelectRowAt#と関係ないので動作します. UITableViewが編集モードの時にも無効にしたい場合,上の記述に加えて以下を記述する. tableView.allowsSelectionDuringEditing = false 特定のcellに適用する場合 cell.selectionStyle = .none 参考 How can I disable the UITableView selection?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutterでカスタムアイコンを使う方法

はじめに Flutterでアイコンを利用するときは既存のIconを使って以下のように表示しますよね? const Icon(Icons.home); しかし既存のアイコンでは表現できない時や何かもの足りない時があるかと思います。 svgファイルをインポートして一個一個 flutter_svgを使って表示するのも面倒なのでカスタムアイコンをいい感じにFlutterで導入できる方法をご紹介します? やり方 FlutterIcon.comというサイトを使って実装して行きます。 こんな感じのサイトです。 このサイトでは自分でインポートしたSVGはもちろん、MaterialIconを含め色々なアイコンを自分で選びDartで使える形でダウンロードすることが可能です。 1. アイコンを選ぶ まずは使いたいアイコンを選んでいきます。FlutterIconのサイトに既存であるものはクリック、自分で用意したsvg形式のアイコンはサイト上部にドラッグしてクリックすることで選択することが可能です。 2. ダウンロード 使いたいアイコンが選択できたら、ダウンロードをして行きます。 サイトの右上にテキストフィールドとDownloadボタンがあるので、テキストフィールドにアイコンセットのタイトルを設定(初期値はMyFlutterApp)してDownloadボタンを押してダウンロードします。 ダウンロードしたファイルを解凍すると以下のようなファイルが入っています。fontsフォルダの中にはttf形式のファイルがあります。 3. プロジェクトへ設置 ダウンロードしたファイルをインポートしていくことでflutterプロジェクト内で選択したカスタムのアイコンが利用できるようになります! assetsフォルダにttfファイルを設置してpubspec.yamlに以下のように書くことでアイコンセットをインポートできます。 assets: - assets/ #assetsフォルダ以下を読み込む fonts: - family: MyFlutterApp #アイコンセットのタイトル fonts: - asset: assets/MyFlutterApp.ttf #設置したファイル 4. インポートしたアイコンを使う まずは、ダウンロードしたフォルダ内にあるdartファイルを設置して行きましょう! dartファイルには以下のようにIconDataが定義されているクラスがあります。 import 'package:flutter/widgets.dart'; class MyFlutterApp { MyFlutterApp._(); static const _kFontFam = 'MyFlutterApp'; static const String _kFontPkg = null; static const IconData insert_link = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData insert_photo = IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg); static const IconData alexandergreat = IconData(0xf058, fontFamily: _kFontFam, fontPackage: _kFontPkg); } あとはこれを好きな場所で呼び出してあげるだけでアイコンが利用できるようになります? 簡単! @override Widget build(BuildContext context) { return Center( child: Icon(MyFlutterApp.alexandergreat), ); } これでカスタムしたアイコンが表示できるようになりました! (画面のアイコンは拾ったsvg) 最後に 今回はFlutterで簡単にカスタムしたアイコンを表示する方法を紹介しました? よりアプリの表現に幅を広げたい場合は使ってみてはいかがでしょうか? freesvg.orgのようなサイトでいろんなsvgファイルがダウンロードできるので使ってみるのも面白いかもしれません? またTwitterの方でもFlutterの情報も発信していくのでよろしければフォローお願いします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Xcode]Xcodeのエディタ設定-表示編

はじめに 久しぶりにXcodeのエディタの設定を見直す機会があったので、それぞれの機能や設定の仕方をまとめることにしました。 今回はそのうち表示に関連するものです。 本記事においては、以下の環境に基づいて記述しています。 Xcode:Version 12.4 (12D4e) 本編 以下の項目は全てXcode->Preferences->TextEditingから設定できます。 Displayの部分を上から順に概要を示します。 Show Line numbers その名の通りエディタ画面の左側の行番号を表示するかしないかの設定です。 普通に表示してた方がいいと思います。 Off On Code folding ribbon コードをブロックごとに折り畳むことのできる機能です。デフォルトではオフになってたような気がします。 VSCodeとかではよく使ってたので見つけた瞬間すぐオンにしました。割と便利です。 ↓こんな感じ Mark separators オンにすると//MARK: -コメントを記述した部分に境界線を表示してくれます。 Off On Code coverage iteration counts これはUnitTestに関連する機能です。オンにするとtestを実行後に、関数の呼び出し回数を表示してくれます。 Off On Page guide at column 1行に何文字までにするかを設定できます。これをオンにした時、設定した文字数を超える部分にガイドが表示されます。 1行何文字ルールとかある人とかには便利だとは思いますが自分は使ってません。 Off On Highlight instances of selected symbol オンにすると関数や変数などをクリックした時に同じシンボルをハイライトしてくれるようになります。また、クリック後にどれぐらいの秒数後にハイライトされるかも設定できるようになっています。 他人のコード読む時とかないと困るレベルくらいに使ってます。すぐにオンにすべき。 Off On Documentation Comments Fade doc comment delimitters オンにするとドキュメントコメントの///を目立たないようにしてくれます。 Off On Fade doc comment markup delimitters オンにするとドキュメントコメントの-を目立たないようにしてくれます。 Off On Line Wrapping Wrap Line to editor width オンにすると1行が画面に収まらない時に、折り返して表示してくれるようになります。また、折り返した行を本来の行に対してどれだけインデントするかの設定ができます。 ↓こんな感じの機能です。 画面幅が十分 画面は幅が不十分 Editor over scroll プログラムファイルの一番最後の行にいった時に、その下にどれくらいのスペースを表示するかの設定です。 None Medium Large 番外編 ・スペルミスをしたときに赤い下線で示してくれる機能 Edit->Format->Spelling and Grammar->Check Spelling While Typeで設定できます。 こんな感じ ・タブやスペース、改行の可視化 Editor->invisibles で設定できます。 こんな感じ おわりに 読んでいただいてありがとうございます。ご指摘ご質問等ございましたらコメントかDMまでください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む