- 投稿日:2019-06-25T23:56:26+09:00
SwiftUIでURL通信を伴うアプリを実装してみる
Introduction
WWDCで大きな発表がありましたね、そうSwiftUI。
ただSwiftUIでAPIを使ったアプリはどう実装するんだと思い、
デモアプリを作ってみました。Coding
- 実装したこと
- ContentViewにNetworkManagerをStateとしてプロパティにもつ
- NetworkManagerのinitでリクエストを行う
- レスポンスをNetworkManagerのプロパティにupdateする
- updateされた時にViewへの通知を行う
※APIとModelに関してはお持ちのAPIで書き換えお願いします。
ContentView
struct ContentView : View { @State var networkManager = NetworkManager() var body: some View { NavigationView { List ( networkManager.models.identified(by: \.name) ) { Text($0.name) } .navigationBarTitle(Text("Model")) } } }NetworkManager
class NetworkManager: BindableObject { var didChange = PassthroughSubject<NetworkManager, Never>() var models: [Model] = [] { didSet { didChange.send(self) } } init() { guard let url = URL(string: "https://xxx.json") else { return } URLSession.shared.dataTask(with: url) { (data, _, _) in guard let data = data else { return } let models = try! JSONDecoder().decode([Model.self], from: data) DispatchQueue.main.async { self.models = models } }.resume() } }Model
struct Model: Decodable { let name: String }Figure
Impression
SwiftUIでAPIからのView生成まで作ることができました。
既存のフレームワークより大分簡潔にコード量も少なく書くことができます。
まだまだ出来ることが未知なフレームワークですが、今後が楽しみです!Reference
https://developer.apple.com/videos/play/wwdc2019/204/
SwiftUI Fetching JSON and Image Data with BindableObject - YouTube
- 投稿日:2019-06-25T23:00:42+09:00
bind(バインディング)の概念について(Rxswift)
bind(バインディングってそもそも何)
Rxswiftの.bindなどで用いられるバインディングですがプログラムに置き換えると結局どういうことなの?
と感じたのでまとめました
・バインディングは.bindしなくてもそもそもできるもの
・Timer
と似たようなもんで監視対象が時間ではなくViewなどになった
だけ// 1秒'たったら'getInfoを実行する let timerGetInfo = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(getInfo), userInfo: nil, repeats: true) timerGetInfo.fire()// hugaを満たしたらhogeにバインディング .map { huga } .bind(to: hoge)・まぁ用は
紐付ける
ぐらいに考えとけばいい
→深く掘り下げすぎなくても分からなければならなくなってからやってったらいい、キャリア積んだ人でもこの辺のことは探り探りで進めていくことも多い結論
あんま気にすんな
- 投稿日:2019-06-25T23:00:42+09:00
bind(バインディング)の概念について(Rxswift)(kboy流)
bind(バインディングってそもそも何)
Rxswiftの.bindなどで用いられるバインディングですがプログラムに置き換えると結局どういうことなの?
という疑問がググってもなかなか消化できずkboyさんにお聞きしたところ
・バインディングは.bindしなくてもそもそもできるもの
・Timer
と似たようなもんで監視対象が時間ではなくViewなどになった
だけ// 1秒'たったら'getInfoを実行する let timerGetInfo = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(getInfo), userInfo: nil, repeats: true) timerGetInfo.fire()// hugaを満たしたらhogeにバインディング .map { huga } .bind(to: hoge)・まぁ用は
紐付ける
ぐらいに考えとけばいい
→深く掘り下げすぎなくても分からなければならなくなってからやってったらいい、キャリア積んだ人でもこの辺のことは探り探りで進めていくことも多い結論
あんま気にすんな
kboyさん
ARKit開発は国内トップの凄腕iOSエンジニア
MENTAというサービス内でiOSアプリ開発のメンターをされています(1万円/月)
- 投稿日:2019-06-25T18:03:17+09:00
iPhoneでテザリング時にUIの表示崩れを見かけたので原因を探ってみる
iPhoneでのテザリング
iPhoneでのテザリング時には画面上部にOS側で使用する領域が増える。
インターネット共有: X台接続中
の分、画面が縦に狭くなる。
通常時 テザリング時 これが影響し、以下のような挙動のアプリを割と見かける。
- スプラッシュ画面のロゴの中段が潰れる
- スクロールが最上部/最下部までスクロールできない
- 画面上部が潰れる
- フッタボタン群が一部しか表示されない
- 下スクロール時にフッタボタン群が隠れるような動作をする場合、上スクロールでフッタボタン群が一部しか表示されない
通常時 テザリング時 原因を推測してみる
表示だけの問題で、UI操作自体には問題ないようなので、
高さ取得に問題があると推測する。検証
コードで通常時/テザリング時の画面の高さを取得する。
検証時の端末:iPhone 8 Plus(iOS12.3.2)
高さ取得 コード 通常時 テザリング時 画面の高さ UIScreen.main.bounds.size.height 568 568 ディスプレイの高さ UIScreen.main.nativeBounds.size.height 1481 1481 基底viewの高さ ViewController#view.frame.height 568 548 違いがあるのは、基底viewの高さのみ。
iPhone Xシリーズでの検証をしていませんが、
見た目では、テザリング時にOSが使用する領域は変わらないため、
表示上の問題はなさそうです。
(テザリング時はSafe Areaの表示領域が変わらず見た目が変わるだけです)対策案
(実際にコードを書いて対策案を検証した訳ではないため、現段階では案のみです)
画面の大きさを取得し、それを元に表示領域を判定する
と
テザリング時に表示領域が狭くなった場合に、
画面自体の大きさは変わっていないため、
表示やUI操作に問題が発生する可能性があります。また、アプリ画面表示中に別のiPhoneからの操作により、
突然テザリング時の表示に切り替わるため、
画面初期表示時の基底viewの高さもあてになりません。これらに柔軟に対応するためには、
AutoLayout
とNSLayoutConstraint
で画面を定義し、
自動的にサイズや位置を調整したり、
NSLayoutConstraint
で上限/下限などを定義などの対策になると思われます。追記:対策案2
- UIApplication.didChangeStatusBarFrameNotification
OSが使用するステータスバーの表示領域変更を検知できるので、
アプリ画面表示中の突然のテザリング表示への切り替わり
に対応できます。
- 投稿日:2019-06-25T15:59:26+09:00
【備忘録】同一のUITableViewで複数のUITableViewCellを表示する
流れ
Register
親となる(
UITableView
が設置されている)ViewController#viewDidLoad
内で, 使用したいセルをregister
する.override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "FirstCell", bundle: Bundle.main), forCellReuseIdentifier: "FirstCell") tableView.register(UINib(nibName: "SecondCell", bundle: Bundle.main), forCellReuseIdentifier: "SecondCell") }Dequeue Reusable Cell
tableView
メソッド内で条件を指定してそれぞれdequeueReusableCell
する.if condition { let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath) as! FirstCell return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath) as! SecondCell return cell }Create Xib File
それぞれで表示する
.xib
ファイルを作成すれば完了.hirrot.
- 投稿日:2019-06-25T15:59:26+09:00
同一のUITableViewで複数のUITableViewCellを表示する
流れ
Register
親となる(
UITableView
が設置されている)ViewController#viewDidLoad
内で, 使用したいセルをregister
する.override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "FirstCell", bundle: Bundle.main), forCellReuseIdentifier: "FirstCell") tableView.register(UINib(nibName: "SecondCell", bundle: Bundle.main), forCellReuseIdentifier: "SecondCell") }Dequeue Reusable Cell
tableView
メソッド内で条件を指定してそれぞれdequeueReusableCell
する.if condition { let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath) as! FirstCell return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath) as! SecondCell return cell }Create Xib File
それぞれで表示する
.xib
ファイルを作成すれば完了.hirrot.
- 投稿日:2019-06-25T15:59:26+09:00
[memorandum] 同一のUITableViewで複数のUITableViewCellを表示する
流れ
Register
親となる(
UITableView
が設置されている)ViewController#viewDidLoad
内で, 使用したいセルをregister
する.override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "FirstCell", bundle: Bundle.main), forCellReuseIdentifier: "FirstCell") tableView.register(UINib(nibName: "SecondCell", bundle: Bundle.main), forCellReuseIdentifier: "SecondCell") }Dequeue Reusable Cell
tableView
メソッド内で条件を指定してそれぞれdequeueReusableCell
する.if condition { let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath) as! FirstCell return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath) as! SecondCell return cell }Create Xib File
それぞれで表示する
.xib
ファイルを作成すれば完了.hirrot.
- 投稿日:2019-06-25T12:08:36+09:00
Swift 動画撮影をlandscape(横向き)で
AVCaptureMovieFileOutputの設定
まず, ファイル出力の設定時点で,向きを変更可能にします。(デフォルトは変更不可)
captureSession.addOutput(videoFileOutput) let videoDataOuputConnection = videoFileOutput.connection(with: .video) videoFileOutput.setRecordsVideoOrientationAndMirroringChangesAsMetadataTrack(true, for: videoDataOuputConnection!)録画開始時にファイル出力の向きをデバイスの向きに合わせます。
let videoDataOuputConnection = videoFileOutput.connection(with: .video) let orientation = UIDevice.current.orientation videoDataOuputConnection!.videoOrientation = AVCaptureVideoOrientation(rawValue: orientation.rawValue)!参考:
How to record landscape video in a portrait application? (Swift 2, iPhone)
表示映像の設定
回転した際に向きを取得します。
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator) coordinator.animateAlongsideTransition( nil, completion: {(UIViewControllerTransitionCoordinatorContext) in //画面の回転後に向きを教える。 if let orientation = self.convertUIOrientation2VideoOrientation({return UIApplication.sharedApplication().statusBarOrientation}) { videoPreviewLayer?.connection.videoOrientation = orientation } } ) } func convertUIOrientation2VideoOrientation(f: () -> UIInterfaceOrientation) -> AVCaptureVideoOrientation? { let v = f() switch v { case UIInterfaceOrientation.Unknown: return nil default: return ([ UIInterfaceOrientation.Portrait: AVCaptureVideoOrientation.Portrait, UIInterfaceOrientation.PortraitUpsideDown: AVCaptureVideoOrientation.PortraitUpsideDown, UIInterfaceOrientation.LandscapeLeft: AVCaptureVideoOrientation.LandscapeLeft, UIInterfaceOrientation.LandscapeRight: AVCaptureVideoOrientation.LandscapeRight ])[v] } }
- 投稿日:2019-06-25T09:12:25+09:00
Asset Catalogで同じ名前の別画像を利用する
はじめに
- Xcodeのアセットカタログ(デフォルトではAssets.xcassets)は、画像などのリソースを管理します
- アセットカタログ内で、フォルダ分けも可能です
- その際、別のフォルダに同じ名前の画像を置きたくなることもあります
- その場合の扱い方です
検証環境
- Xcode 10.2.1
- iOS 12.2
- Swift 5
フォルダにNamespaceを付与する
デフォルトの状態(Namespaceなし)
- この画像の例では、
bird
という画像がforest
フォルダとsea
フォルダの両方に配置されています- ですが、デフォルトの状態ではフォルダ名は無視されるので、これらの画像には
bird
という名前でアクセスすることになり、区別ができませんフォルダ名付きでのアクセス(Namespaceあり)
- アセットカタログ内でフォルダを選択し、Attributes Inspectorから
Provides Namespace
にチェックを入れると、そのフォルダ名がNamespaceとして利用されます
- フォルダの色も黄色から水色に変わっていますね
- この画像の例では、それぞれ
forest/bird
とsea/bird
という名前で区別されますこんな感じで、コード内でもInterface Builderでも参照できます
imageView.image = index == 0 ? UIImage(named: "forest/bird") : UIImage(named: "sea/bird")まとめ
- Namespaceがあると、格段に管理がしやすくなりますね
- 今回作成したサンプルコードは、GitHubに置きました
- なお、この記事は私のブログ記事からの転載です
参考
- 投稿日:2019-06-25T06:20:12+09:00
[Swift] 初心者向け 失敗しないScrollViewの設定のしかた
はじめに
初めてScrollviewの設定をする際に、少々手こずったので自分のためにまとめておきました。
自分なりに一番納得した手順を説明します。何か指摘や質問があれば気軽にコメントください。手順
1.ScrollViewの追加
元からあるViewにScrollViewを追加します。
2.AutoLayoutの設定
ScrollViewに対し、top,bootom,left,rightそれぞれを以下のように0に設定する。
以下のように対象がSafeAreaになっていることを確認します。
3.ScrollViewの上にViewを追加
1と同じようにScrollViewの上にViewを追加します。4.追加したViewに対しAutoLayoutを設定
まず、2と同じように追加したviewのtop,bottom,left,rightを0に設定します。
次に、commandキーを押しながら追加したviewとScrollViewを選択し、以下のようにequal Widthに設定します。
(注意)equal Heightには設定しないようにしてください。
5.スクロールできるviewの高さを決める。
追加したviewの高さを好きな値に設定して完成。
(今回は1000にしました。)
StoryBoardの設定
storyboardでUIの作成を行う時には以下の設定をして、StoryBoardに表示されるviewの大きさを変更します。
1. main.storyboardでviewControllerを選択
2. 右のウィンドウからsimulatedSizeをFreedomに設定する。
以下のようにsimulatedSizeをFreedomに設定し、Heightの値をview全体が表示される値に設定します。
3.以上
さいごに
ScrollViewの設定についてまとめました。
次は、僕がお気に入りのライブラリについて紹介していきます。
気軽にコメントや質問ください。
- 投稿日:2019-06-25T01:44:47+09:00
iOS12 以下をサポートするプロダクトで SwiftUI の恩恵を得る方法
はじめに
SwiftUI に注目が集まる一方、iOS13 未満をサポートバージョンから切り捨てるまで導入が困難なのも事実です。今回はこの問題点に着目し、 iOS12 以下も今までどおりサポートしつつ SwiftUI と Xcode11 を利用したプレビュー機能を実装する方法をご紹介します。
参考に
AkkeyLab/StoryboardPreviewsBySwiftUI
この記事は、サンプルからの抜粋となります。詳細はこちらをご覧ください。Storyboard Preview by SwiftUI
WWDC 報告会にて同様の内容で発表も行っております。ぜひご覧ください。環境
Xcode 11 β
macOS Catalina β実現すること
- iOS12 以下をサポートしつつ Xcode Previews を利用する
- 既存の Storybord や xib ファイルをプレビューさせる
実現方法
- Xcode Previews 専用のターゲットを作る
- プレビューさせる画面に関連するソースコードとプレビュー用の swift ファイルが含まれており、 iOS13 以降をターゲットバージョンとする
- 対象のクラスを
UIViewRepresentable
に準拠させる
updateUIView
メソッド内で施した変更がプレビューに即時反映されるPreviewProvider
に準拠させた struct を定義
static var previews: some View
でプレビューに必要な処理を施す※ UIViewController をプレビューさせる場合はそれぞれ
UIViewControllerRepresentable
,updateUIViewController
となります。import SwiftUI import UIKit #if DEBUG // Create new for preview struct UserDetailBasicInfoCellPreviews: PreviewProvider { static var previews: some View { Group { // Sets the format of the preview UserDetailBasicInfoCell() .previewLayout(.fixed(width: 320, height: 100)) .previewDevice(PreviewDevice(rawValue: "iPhone SE")) UserDetailBasicInfoCell() .previewLayout(.fixed(width: 414, height: 100)) .previewDevice(PreviewDevice(rawValue: "iPhone XS Max")) } } static var platform: PreviewPlatform? = .iOS } // UserDetailBasicInfoCell.swift // UserDetailBasicInfoCell.xib extension UserDetailBasicInfoCell: UIViewRepresentable { typealias UIViewType = UserDetailBasicInfoCell func makeUIView(context: Context) -> UserDetailBasicInfoCell { return Self.instantiate() } func updateUIView(_ uiView: UserDetailBasicInfoCell, context: Context) { // Make parameter change for preview } } #endif利点
- 表示・非表示が切り替わるパーツが多い場合のデバッグ強化
- 複数端末(画面サイズ)でのレイアウト確認が一度に行える
※サンプルの README を見ればもっとイメージしやすいかもしれません。
欠点
- 前準備がある程度必要になる
最後に
今年の秋が楽しみですね!
- 投稿日:2019-06-25T00:58:37+09:00
iOSアプリからOutlookの予定表を取得する方法(Swift)
はじめに
Microsoft Graph APIを使って、iOSアプリからOutlookの予定表を取得する方法を紹介します。
「Microsoft Graph API」とは?
Microsoft 365のデータにアクセスするためのAPIです。
詳細は公式ドキュメントをご参照ください。
https://docs.microsoft.com/ja-jp/graph/overview前提条件
- Microsoftアカウントを取得している
- アプリ登録の権限を持つAzure AD(Active Directory)が存在する
Azure ADの追加方法は以下の通り
https://blogs.technet.microsoft.com/jpazureid/2018/01/16/azuread-operation/手順
公式ドキュメントに沿って実装します。
https://docs.microsoft.com/ja-jp/azure/active-directory/develop/quickstart-v2-ios#option-2-register-and-manually-configure-your-application-and-code-sample選択肢1(自動)はやり方がわからなかったので、今回は選択肢2(手動)で行います。
アプリケーションの登録
まず、Azureにアプリを登録します。
以下のページにアクセスし、[+新規登録]をクリックします。
https://aka.ms/MobileAppReg
[サポートされているアカウントの種類]では、「この組織のディレクトリ内のアカウントのみ」を選択するとシングルテナントアプリ、他の2つを選択するとマルチテナントアプリとなります。
これでアプリの登録は完了です。
Bundle IDの更新
次に、登録したアプリにBundle IDを追加します。
※公式ドキュメントの手順がわからなかったので、異なる方法で行っています。[Step 1: Configure your application]にある[Make this change for me]をクリックします。
[バンドル ID]にiOSアプリのBundle Identifierを入力し、[更新する]をクリックします。
後述するサンプルコードのBundle IDは
com.microsoft.identitysample.MSALiOS
です。
※一度Bundle IDを変更すると、[Make this change for me]が非表示になるため、再度Bundle IDを変更できなくなります。
変更する方法を知っている方がいらっしゃったら教えてくださいこちらのページの
kClientID
とkAuthority
は後で使うので、ページは開いたままにしてください。実装
サンプルコードのダウンロード
以下からサンプルコードをダウンロードします。
https://github.com/Azure-Samples/active-directory-ios-swift-native-v2/archive/master.zipクライアントIDと認証URLの更新
MSALiOS.xcworkspaceをXcodeで開きます。
クライアントIDと認証URLを、先ほど開いたページに記述されている値に変更します。
ViewController.swift- let kClientID = "{デフォルトのクライアントID}" + let kClientID = "{自アプリのクライアントID}" … let kAuthority = "https://login.microsoftonline.com/common" // マルチテナントアプリ let kAuthority = "https://login.microsoftonline.com/{自アプリのディレクトリ(テナント)ID}" // シングルテナントアプリ
kAuthority
はマルチテナントアプリとシングルテナントアプリで指定する値が異なります。マルチテナントやシングルテナントについては、公式ドキュメントをご参照ください。
https://docs.microsoft.com/ja-jp/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant権限の追加
必要に応じて権限を追加します。
私は予定表を取得したいので、Calendars.Read
を追加しました。予定表の取得に必要な権限は、以下に記載されています。
https://docs.microsoft.com/ja-jp/graph/api/user-list-calendarview?view=graph-rest-1.0&tabs=csViewController.swiftlet kScopes: [String] = ["https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Calendars.Read"]URLスキームの変更
Info.plistに記述されているURLスキームを以下のように書き換えます。
サンプルアプリだとこのままでも動作しますが、書き換えておくのがいいです。Info.plist<array> <dict> <key>CFBundleURLSchemes</key> <array> - <string>msauth.com.microsoft.identitysample.MSALiOS</string> + <string>msauth.$(PRODUCT_BUNDLE_IDENTIFIER)</string> </array> </dict> </array>デバッグ
ここまでできたらデバッグして動作確認します。
[Call Microsoft Graph API]をタップします。
Microsoftアカウントを入力し、[Next]をタップします。
[Result from Graph:]にJSONデータが表示されたらログイン成功です!
予定表の取得
自分の1週間の予定表を取得する処理を実装します。
GraphエクスプローラーにサインインしてAPIを試し、クエリやレスポンスボディを把握すると実装しやすいです。
https://developer.microsoft.com/ja-jp/graph/graph-explorerサンプルコードの
getContentWithToken()
メソッドをコピぺし、メソッド名とURLのみ変更すればOKです。ViewController.swiftprivate func getCalendarWithToken() { let formatter = DateFormatter.fullISO8601 let startDateTime = formatter.string(from: Date()) let endDateTime = formatter.string(from: Date(timeIntervalSinceNow: 60*60*24*7)) let urlString = kGraphURI + "calendarview?startdatetime=" + startDateTime + "&enddatetime=" + endDateTime // Specify the Graph API endpoint let url = URL(string: urlString) var request = URLRequest(url: url!) // Set the Authorization header for the request. We use Bearer tokens, so we specify Bearer + the token we got from the result request.setValue("Bearer \(self.accessToken)", forHTTPHeaderField: "Authorization") URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { self.updateLogging(text: "Couldn't get graph result: \(error)") return } guard let result = try? JSONSerialization.jsonObject(with: data!, options: []) else { self.updateLogging(text: "Couldn't deserialize result JSON") return } self.updateLogging(text: "Result from Graph: \(result))") }.resume() } }DateFormatter+ISO8601.swiftimport Foundation extension DateFormatter { static let fullISO8601: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" formatter.calendar = Calendar(identifier: .iso8601) formatter.timeZone = TimeZone(secondsFromGMT: 0) formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() }
getContentWithToken()
メソッドを差し替え、デバッグします。(2箇所)ViewController.swift- self.getContentWithToken() + self.getCalendarWithToken()おわりに
あとはこれらのコードを参考に製品アプリを実装すればOKです。
iOSアプリからMicrosoftのデータを取得するのはなかなか大変でした
参考リンク
- 【Swift】型を使うという意味を考える (Codable再入門) - Qiita
ISO 8601フォーマットの日付を文字列に変換する方法を参考にさせていただきました。
- 投稿日:2019-06-25T00:43:32+09:00
ダークモード適用を回避する方法
はじめに
iOS13から遂にダークモードが導入されました。
iOS13 プレビュー記事今後のiOSアプリ作成にはダークモードを適用が前提となっています。
しかし、アプリ独自のレイアウトを設定しているために、
すぐにはダークモードを適用できない場合があると思います。そこで今回は、ダークモードを回避する方法を共有します。
UIViewControllerごとに設定
UIViewControllerに追加された
overrideUserInterfaceStyle
の値を指定します。
デフォルトはunspecified
(指定なし)です。ユーザーの設定によって変化します。
値 説明 unspecified 指定なし(デフォルト) light ライトモード(明るい外観) dark ダークモード(暗い外観) UIUserInterfaceStyle - UIKit | Apple Developer Documentation
サンプルコード
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 常にライトモード(明るい外観)を指定することでダークモード適用を回避 self.overrideUserInterfaceStyle = .light } }アプリ全体に適用する
アプリ全体に適用するには
Info.plist
のUIUserInterfaceStyle
パラメータをLight
にします。
これで常にライトモードになります。
なおAutomatic
(自動)Dark
(ダークモード)を指定することもできます。
なお設定しない場合や不正な値が入力された場合は常にLight
が採用されます。まとめ
これらの方法でダークモードの適用を回避することができました。
しかし、Appleはダークモードの適用を推奨しているので、
いずれかのタイミングでアプリにダークモードを適用させる対策が必要です。この対応はあくまで一時的な対応か外観を変化させたくない特段の事情がある場合に止めるべきでしょう。
参照
Choosing a Specific Interface Style for Your iOS App | Apple Developer Documentation
外観モードの変更方法についてImplementing Dark Mode on iOS - WWDC 2019 - Videos - Apple Developer
WWDC2019のダークモードに関するセッション。動画やサンプルコードがあります。