- 投稿日:2021-01-21T21:29:31+09:00
iPhoneの壁紙に飽きたので、毎日ちがう壁紙を設定してほしい
iPhoneを使い始めてしばらく経ちましたが、壁紙を見飽きてしまいました。
毎日ちがう壁紙を設定したい!しかも自動で変更してほしい!もっと言えば、探すのも面倒なので、いい感じの壁紙を見繕って来てほしい!ということで作りました。
比較的新しめのiPhone標準アプリ、ショートカットで夢を叶えることができました!いい感じの壁紙
今回は、Bingの壁紙を使うことにしました。毎日いい感じに変わり、PC/スマホともに壁紙をダウンロードすることもできます。
作成したショートカットを共有します
作成したショートカットの共有リンクを掲載しておきます。ご自由にお使いください。
https://www.icloud.com/shortcuts/1372925c0aa14d5399ae02c907d1be26今回はBingを使いましたが、いい感じの画像をゲットできるAPIなどがあれば、お好みで上記ショートカットをカスタマイズしても良いと思います。
なお、壁紙の変更はiOS14.3以上のみ利用可能らしいです。(未確認)自動的に壁紙を変更する
上記ショートカットだけでは、壁紙は自動で変更されません。
ショートカットアプリのオートメーション→個人用オートメーションを作成→時刻からお好きな時刻を設定→アクションを追加から、ショートカットを実行を設定します。(ショートカットを実行はスクリプティングの中にあります)
- 投稿日:2021-01-21T17:42:20+09:00
【Swift】Combine + APIKit で複数リクエストを直列で実行するサンプル (ついでに並列も)
Combine.frameworkを体験する取っ掛かりとして
APIKitのRequestにPublisherを生やして
複数の非同期通信を直列・並列で叩くサンプルを実装してみました。まだまだCombineニュービーですのでオススメの方法や
間違い等有りましたらご指摘頂けると幸いです??♂️環境
- Xcode 12.1
- APIKit 5.1.0
- SwiftUI
サンプルコード
https://github.com/SatoshiN303/RequestsWithCombine
プロジェクト構成
APIKitにPublisherを生やす実装方法については
Developers.IO さんの以下の記事が
非常にわかりやすかったのでほぼコピさせていただきました。
[iOS 13] SwiftUI + Combine + APIKitでインクリメンタルサーチ今回のサンプルコードでは複数の通信を叩くために
GitHub REST APIを使う形に変更しています。処理の流れ
主な登場人物は上記赤枠の3つです。
処理の流れは以下になります。
ContentView
のUISearchBar
の入力を受け取るSampleModel
でリクエストを作成して直列の通信を開始する- 最初に
/search/repositories
を叩く- 上記のレスポンスを利用して
/repos/{owner}/{repositry}
を叩く- 通知された結果を表示する
直列で叩く(Publisher)
最初に
/search/repositories
を叩き、レスポンスを利用して
/repos/{owner}/{repositry}
を叩いてみます。
正直最初のリクエストで欲しいデータは取得できますが学びということで。// `/search/repositories` のリクエスト作成 let searchRepositories = SearchRepositoriesRequest(query: searchText).publisher.eraseToAnyPublisher() self.requestCancellable = searchRepositories // レスポンスはSearchRepositoriesResponse型なのでitems.firstをとりあえず取得 .compactMap { $0.items.first } // 上記レスポンスを利用して GetRepositoyRequest を作成 .flatMap { (repository) -> AnyPublisher<GetRepositoyRequest.Response, Error> in /* 処理を繋げるとコンパイラが型がネストして解釈して複雑な型になるため * eraseToAnyPublisher()で型消去した * AnyPublisher<GetRepositoyRequest.Response, Error> を返す */ GetRepositoyRequest(owner: repository.owner.login, repositry: repository.name).publisher.eraseToAnyPublisher() } .receive(on: DispatchQueue.main) .sink { (completion) in switch completion { case .finished: debugPrint("request finished") case let .failure(error): debugPrint("request failed : \(error)") } } receiveValue: { [weak self] repository in // `/repos/{owner}/{repositry}` のレスポンスをContentViewに渡す self?.items = [repository] }補足リンク eraseToAnyPublisher()での型消去について
https://developer.apple.com/documentation/combine/publisher/3241548-erasetoanypublisher
Futureを使って直列で叩く
FutureはSubscribeされなくても
インスタンスが生成されたタイミングで即時実行されるので
とりあえずクロージャーで返すメソッドを作って処理を繋げてみました。
こちらも本来であれば再利用のためにAPIKitに生やすのが望ましいでしょうか。// MARK: - Futureを使って直列で叩く self.requestCancellable = fetchRepos(query: searchText) .compactMap { $0.items.first } .flatMap { [unowned self] repository in self.fetchRepository(owner: repository.owner.login, repo: repository.name) }.sink { (completion) in switch completion { case .finished: debugPrint("request finished") case let .failure(error): debugPrint("request failed : \(error)") } } receiveValue: { [weak self] repository in self?.items = [repository] } // MARK: - 各リクエストの生成処理 // ※Futureは即時実行される => インスタンスが生成されたタイミングでSubscribeをしなくても処理が走る private func fetchRepos(query: String) -> Future<SearchRepositoriesRequest.Response, Error> { return Future<SearchRepositoriesRequest.Response, Error> { promise in Session.send(SearchRepositoriesRequest(query: query), callbackQueue: .main) { (result) in switch result { case .success(let res): // 処理成功 promise(.success(res)) case .failure(let error): // 処理失敗 promise(.failure(error)) } } } } // ※Futureは即時実行される => インスタンスが生成されたタイミングでSubscribeをしなくても処理が走る private func fetchRepository(owner: String, repo: String) -> Future<GetRepositoyRequest.Response, Error> { return Future<GetRepositoyRequest.Response, Error> { promise in Session.send(GetRepositoyRequest(owner: owner, repositry: repo), callbackQueue: .main) { (result) in switch result { case .success(let res): // 処理成功 promise(.success(res)) case .failure(let error): // 処理失敗 promise(.failure(error)) } } } }ついでに並列でも叩いてみる
Publishers.Zipに渡せば並列で叩けるので
/search/repositories
と/search/users
を同じクエリで叩いてみます。let searchRepositories = SearchRepositoriesRequest(query: searchText).publisher.eraseToAnyPublisher() let searchUsers = SearchUsersRequest(query: searchText).publisher.eraseToAnyPublisher() self.requestCancellable = Publishers.Zip(searchRepositories, searchUsers) .receive(on: DispatchQueue.main) .sink { _ in } receiveValue: { (repos, users) in print(repos) print(users) }ついでに補足比較: RxSwiftで直列で叩く場合
RxSwiftの場合はSessionにObservableを生やしてflatMapで繋ぐ感じでしょうか。
extension APIKit.Session { func rx_sendRequest<T: Request>(request: T) -> Observable<T.Response> { return Observable.create { observer in let task = self.send(request) { result in switch result { case let .success(res): observer.on(.next(res)) observer.on(.completed) case let .failure(err): observer.onError(err) } } return Disposables.create { task?.cancel() } } } } // Observableで包んで返す func searchRepositories(request: SearchRepositoriesRequest) -> Observable<SearchRepositoriesRequest.Response> { return Session.rx_sendRequest(request: request) } func getRepository(request: GetRepositoryRequest) -> Observable<GetRepositoryRequest.Response> { return Session.rx_sendRequest(request: request) } // もろもろ省略 let observable = searchRepositories(request: searchRepositoriesRequest) .flatMap { [unowned self] repos -> Observable<GetRepositoryRequest.Response> in let item = repos.items.first //とりあえず成功してアンラップできてるものとみなす let getRepositoryRequest = getRepositoryRequest(owner: item.owner.login, repo: item.name) return self.getRepository(request: getRepositoryRequest) }.もろもろ続く参考にさせて頂きました。ありがとうございます。
- [iOS 13] SwiftUI + Combine + APIKitでインクリメンタルサーチ
- [iOS]APIKit + Combine + SwiftUIでGitHubリポジトリ検索
- 【Swift】Combineで一つのPublisherの出力結果を共有するメソッドやクラスの違い(share, multicast, Future)
- Combine で非同期処理を直列で実行する(flatMap)
- Modern Networking in Swift 5 with URLSession, Combine and Codable
- Creating a search bar for SwiftUI
- 投稿日:2021-01-21T10:46:46+09:00
結局ScrollViewのConstraintってどうつければいいの?
- UIScrollViewの上下左右をUIViewControllerのViewに合わせる
- ContentViewの上下左右をContent Layout Guideに合わせる
- ContentViewの横幅をFrame Layout Guideに合わせる(Equal Widths)
- ContentViewにオブジェクトを追加して高さを確定させる
名前の通り、Content Layout GuideはUIScrollViewの中身、Frame Layout GuideはUIScrollViewのフレーム領域を示します。
https://qiita.com/owen/items/2fab4a4482834b95e349つまづきやすかったこと
ContentView は自分で UIView を追加して作る
UIViewで囲むのがセオリーとは思うものの、ちょっとAppleさん不親切‥
Content Layout Guide の制約に注意
ContentView に Content Layout Guide の上下左右の制約をかけるとき、ちゃんと画面いっぱいに伸ばしてCtrlで制約を引っ張ってもなぜか+315や+812とズレがつく。見た目では気づきにくいが、これを0に修正しないとスクロールしないので注意。
高さを確定させよ
中のコンテンツの一番下の要素にContentView.bottomとのconstraintを付けないと高さ確定しないので怒られる。
よくある間違い?
ContentViewのbottomをFrame Layout Guideに合わせてしまう
正しい設定では中の要素が少ない時に下側がスカスカに空いてしまう場合がある。それを埋めようとContentViewのBottomをFrameに合わせると、FrameはiPhoneの上下左右の見えてる部分らしく、本来もっと下に伸びるべきContentViewのBottomがiPhoneの底で止まってスクロールしなくなる(伝われ)。
中身が少なく下が空いてしまうときは、ContentViewとScrollViewの背景色をあわせて空いてないように見せるしかないのかしら?
- 投稿日:2021-01-21T08:42:01+09:00
[SwiftUI] SceneDelegate を消して ApplicationDelegate だけにする
これはなに
iOS 13 環境で ReplayKit & UIScene の相性が悪いため、泣く泣く UISceneDelegate を消して UIApplicationDelegate だけにしたときのメモです。
If you must use RPSystemBroadcastPickerView then you should consider not using UIScene, and going back to UIApplicationDelegate.
やったこと
SceneDelegate をバッサリ削除
Info.plist から
UIApplicationSceneManifest
をバッサリ削除します。その後、
SceneDelegate.swift
も不要になるので削除します。(SceneDelegate でやっていたことがある場合は AppDelegate に移行する必要がある)AppDelegate から UIHostingController経由で SwiftUI の View を開く
AppDelegate.swift を次のように書き換えます。
import UIKit import SwiftUI @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window!.rootViewController = UIHostingController(rootView: ContentView) window!.makeKeyAndVisible() return true } }このとき、
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
のような UISceneSession Lifecycle が残っているとうまくアプリが動かないので注意です。
これで一応アプリが起動するようになりますが、動作を保証するものではありません。あくまで自己責任で!
- 投稿日:2021-01-21T06:33:01+09:00
ダークモードにまつわる誤解を解いておく
Githubがダークモードに対応したのが昨年12月。調べて見るところ我々開発者には「目に優しい」と認識されている一方、一般層には単に「格好いいモード」と思われていたりなようです。更にそれだけでもないという記事を見つけました。
概要
以下、流行ってるな~と思ったので、まとめました。「ダークモード」のメリット、デメリット。本当のところ。GitHubをダークモードで使う。
ダークモードに対応したデザイン は2020年始めからトレンドとして指摘されていました。
Redmineでダークモードを使う なんてこともできるようだ。ダークモードのメリット (とされているもの)
- 画面全体の輝度が下がるので目の負担が減る、疲れにくい。
- 特に暗い環境ではコントラストをあげられるので、見やすい。
- 有機ELパネルのディスプレイスマホなら消費電力が下がって電池が長持ちする。
- 「格好いい」感じがするとよく言われる。
引用: https://blog.weekdone.com/why-you-should-switch-on-dark-mode/デメリット (とされているもの)
- スマホでは、未対応のアプリと画面の明るさの差が大きくなる。
- 細かく配慮されていない画面ではアイコンやボタンが見にくいものがある。
- 夜中にスマホの光を懐中電灯代わりにできない(?)。
実際は
「ダークモード」は、本当に“目に優しい”のか? 5つの観点から科学的に検証した結果 が詳しい。以下サマリ。
目の疲れを軽減する?
「確かな証拠を、わたしはまだ見たことがありません」。最善の解決策は、コンピューターの利用を控えることである。
集中力が高まる?
疑問あり。実験参加者に視力検査と校正作業を行ってもらったところ、白い背景に黒い文字のほうが読む速度が速く、見つけた誤字脱字の数も多かった。またTwitterは、ダークモードを有効にしているユーザーのほうが、アプリに費やす時間が長くなることを発見した。だが、これは集中力が増すためではなく、ベッドに入ったまま画面をスクロールするには、ダークモードのほうが不快感が少ないからかもしれない。
「ダークモードは集中力を高め、わたしたちの気持ちを落ち着かせると謳っているアプリはたくさんある。しかし、このモードを搭載する本当の動機は、単にユーザーの利用時間を長くできるからなのかもしれない。」
就寝前はダークモード?
No. 最も効果の高い方法は、眠りにつく1~2時間前には画面を見るのを完全にやめることである。
理想形
文献は記事最後に記載してあります。ダークモードがどこでも何でも良しということはなく、格好いいとか、好みの問題でもないようです。スマホをダークモードにする欠点|逆に目疲れの原因に?、と、子どもには逆に有害とすらする記事も。
例えば、明るい部屋や直射日光の当たる場所では、暗い背景に置かれたテキストが読みづらくなることもある。画面を専ら明るい昼間の屋外で使用したり、屋内・屋外を頻繁に行き来するユースケースでは、例外的な配慮が必要といえる。例えば日没、日の出時刻、自動検知による自動切り替え機能が必要になる。
「バッテリー寿命を延ばす?」という説については、True。
有機ELディスプレイを採用したAndroidのスマートフォンをダークモードにして「Google マップ」のスクリーンショットを表示したところ、消費電力は通常モードより63パーセント少なかったという。(「ダークモード」は、本当に“目に優しい”のか? 5つの観点から科学的に検証した結果 )目には、休息が一番。このような小話ばかり読んでいないで休むときにはしっかり休みましょう。
ダークモードを搭載する例
OS
ブラウザ
チャットアプリ
Facebook Messenger
Skype
SlackSNS
ほか
VSCode
IntelliJ
Eclipse
Github
Todoist
Trello参考
ダークモードのメリット、デメリットはなんですか?
2020年流行するであろうwebデザインのトレンド
The pros and cons of Dark Mode: Here's when to use it and why
The Benefits of Dark Mode: Why should you turn off the lights?
「ダークモード」は、本当に“目に優しい”のか? 5つの観点から科学的に検証した結果
スマホをダークモードにする欠点|逆に目疲れの原因に?
- 投稿日:2021-01-21T05:38:35+09:00
XcodegenでExcluded Architectureにarm64を設定する
Xcodegenを使用したプロジェクトで、arm64アーキテクチャでビルドしようとしたら以下のようなエラーになった。
The linked framework 'Pods_[アプリ名].framework' is missing one or more architectures required by this target: arm64.調べてみたところ、
Build Settings > Architectures > Excluded Architecture
で、Any iOS Simulator SDKを選び、arm64を追加すれば直るとのこと。しかし、Xcodegenを使ってプロジェクトファイルを生成していたので、project.ymlでの書き方がわからず少しハマった。以下のように、ターゲットのsettingsに対して、
EXCLUDED_ARCHS[sdk=iphonesimulator*]: arm64
としたら、うまく生成できたのでそのメモ。project.ymltargets: YourApp: type: application platform: iOS sources: - path: YourApp settings: base: EXCLUDED_ARCHS[sdk=iphonesimulator*]: arm64参考
https://stackoverflow.com/questions/63607158/xcode-12-building-for-ios-simulator-but-linking-in-object-file-built-for-ios
https://medium.com/@khushwanttanwar/xcode-12-compilation-errors-while-running-with-ios-14-simulators-5731c91326e9