20190625のSwiftに関する記事は13件です。

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

image.png

Impression

SwiftUIでAPIからのView生成まで作ることができました。
既存のフレームワークより大分簡潔にコード量も少なく書くことができます。
まだまだ出来ることが未知なフレームワークですが、今後が楽しみです!

Reference

https://developer.apple.com/videos/play/wwdc2019/204/
SwiftUI Fetching JSON and Image Data with BindableObject - YouTube

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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) 

・まぁ用は紐付けるぐらいに考えとけばいい
→深く掘り下げすぎなくても分からなければならなくなってからやってったらいい、キャリア積んだ人でもこの辺のことは探り探りで進めていくことも多い

結論

あんま気にすんな

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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万円/月)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhoneでテザリング時にUIの表示崩れを見かけたので原因を探ってみる

iPhoneでのテザリング

iPhoneでのテザリング時には画面上部にOS側で使用する領域が増える。
インターネット共有: X台接続中の分、画面が縦に狭くなる。

通常時 テザリング時
Screen Shot 2019-06-25 at 17.01.05.png Screen Shot 2019-06-25 at 17.00.28.png

これが影響し、以下のような挙動のアプリを割と見かける。
- スプラッシュ画面のロゴの中段が潰れる
- スクロールが最上部/最下部までスクロールできない
- 画面上部が潰れる
- フッタボタン群が一部しか表示されない
- 下スクロール時にフッタボタン群が隠れるような動作をする場合、上スクロールでフッタボタン群が一部しか表示されない

通常時 テザリング時
Screen Shot 2019-06-25 at 16.54.36.png Screen Shot 2019-06-25 at 16.55.00.png

原因を推測してみる

表示だけの問題で、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の高さもあてになりません。

これらに柔軟に対応するためには、
AutoLayoutNSLayoutConstraintで画面を定義し、
自動的にサイズや位置を調整したり、
NSLayoutConstraintで上限/下限などを定義などの対策になると思われます。

追記:対策案2

  • UIApplication.didChangeStatusBarFrameNotification

OSが使用するステータスバーの表示領域変更を検知できるので、
アプリ画面表示中の突然のテザリング表示への切り替わりに対応できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】同一の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.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

同一の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.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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]
    }
}

参考:
Swift - iOSでビデオカメラを使用時、端末の向きに対応

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Asset Catalogで同じ名前の別画像を利用する

はじめに

  • Xcodeのアセットカタログ(デフォルトではAssets.xcassets)は、画像などのリソースを管理します
  • アセットカタログ内で、フォルダ分けも可能です
  • その際、別のフォルダに同じ名前の画像を置きたくなることもあります
  • その場合の扱い方です

検証環境

  • Xcode 10.2.1
  • iOS 12.2
  • Swift 5

フォルダにNamespaceを付与する

デフォルトの状態(Namespaceなし)

no_namespace.png

  • この画像の例では、birdという画像がforestフォルダとseaフォルダの両方に配置されています
  • ですが、デフォルトの状態ではフォルダ名は無視されるので、これらの画像にはbirdという名前でアクセスすることになり、区別ができません

フォルダ名付きでのアクセス(Namespaceあり)

with_namespace.png

  • アセットカタログ内でフォルダを選択し、Attributes InspectorからProvides Namespaceにチェックを入れると、そのフォルダ名がNamespaceとして利用されます
    • フォルダの色も黄色から水色に変わっていますね
  • この画像の例では、それぞれforest/birdsea/birdという名前で区別されます
  • こんな感じで、コード内でもInterface Builderでも参照できます

    imageView.image = index == 0 ? UIImage(named: "forest/bird") : UIImage(named: "sea/bird")
    

    image_ib.png

まとめ

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] 初心者向け 失敗しないScrollViewの設定のしかた

はじめに

初めてScrollviewの設定をする際に、少々手こずったので自分のためにまとめておきました。
自分なりに一番納得した手順を説明します。何か指摘や質問があれば気軽にコメントください。

手順

1.ScrollViewの追加
 元からあるViewにScrollViewを追加します。
ScreemShot.png

2.AutoLayoutの設定
 ScrollViewに対し、top,bootom,left,rightそれぞれを以下のように0に設定する。
ScreenShot2.png
以下のように対象がSafeAreaになっていることを確認します。
ScreenShot3.png

3.ScrollViewの上にViewを追加
1と同じようにScrollViewの上にViewを追加します。

4.追加したViewに対しAutoLayoutを設定
 まず、2と同じように追加したviewのtop,bottom,left,rightを0に設定します。
 次に、commandキーを押しながら追加したviewとScrollViewを選択し、以下のようにequal Widthに設定します。
 (注意)equal Heightには設定しないようにしてください。
スクリーンショット 2019-06-25 5.46.51.png

5.スクロールできるviewの高さを決める。
追加したviewの高さを好きな値に設定して完成。
(今回は1000にしました。)
スクリーンショット 2019-06-25 6.02.37.png

StoryBoardの設定

storyboardでUIの作成を行う時には以下の設定をして、StoryBoardに表示されるviewの大きさを変更します。
1. main.storyboardでviewControllerを選択
2. 右のウィンドウからsimulatedSizeをFreedomに設定する。
  以下のようにsimulatedSizeをFreedomに設定し、Heightの値をview全体が表示される値に設定します。
スクリーンショット 2019-06-25 6.13.03.png

3.以上

さいごに

ScrollViewの設定についてまとめました。
次は、僕がお気に入りのライブラリについて紹介していきます。
気軽にコメントや質問ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 を見ればもっとイメージしやすいかもしれません。

欠点

  • 前準備がある程度必要になる

最後に

今年の秋が楽しみですね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSアプリからOutlookの予定表を取得する方法(Swift)

はじめに

Microsoft Graph APIを使って、iOSアプリからOutlookの予定表を取得する方法を紹介します。

「Microsoft Graph API」とは?

Microsoft 365のデータにアクセスするためのAPIです。

詳細は公式ドキュメントをご参照ください。
https://docs.microsoft.com/ja-jp/graph/overview

前提条件

手順

公式ドキュメントに沿って実装します。
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
スクリーンショット_2019-06-24_22_17_59.jpg

名前を入力し、[登録]をクリックします。
スクリーンショット_2019-06-24_22_24_23.jpg

[サポートされているアカウントの種類]では、「この組織のディレクトリ内のアカウントのみ」を選択するとシングルテナントアプリ、他の2つを選択するとマルチテナントアプリとなります。

これでアプリの登録は完了です。

Bundle IDの更新

次に、登録したアプリにBundle IDを追加します。
※公式ドキュメントの手順がわからなかったので、異なる方法で行っています。

[クイック スタート] > [iOS]をクリックします。
スクリーンショット_2019-06-24_22_57_34.jpg

[Step 1: Configure your application]にある[Make this change for me]をクリックします。
スクリーンショット 2019-06-24 23.01.49.png

[バンドル ID]にiOSアプリのBundle Identifierを入力し、[更新する]をクリックします。
スクリーンショット_2019-06-24_23_02_32.jpg

後述するサンプルコードのBundle IDは com.microsoft.identitysample.MSALiOS です。
スクリーンショット_2019-06-24_23_02_59.jpg

更新が完了したら緑のチェックが付きます。
スクリーンショット 2019-06-24 23.04.47.png

※一度Bundle IDを変更すると、[Make this change for me]が非表示になるため、再度Bundle IDを変更できなくなります。
変更する方法を知っている方がいらっしゃったら教えてください:bow:

こちらのページの kClientIDkAuthority は後で使うので、ページは開いたままにしてください。

実装

サンプルコードのダウンロード

以下からサンプルコードをダウンロードします。
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=cs

ViewController.swift
let 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]をタップします。
スクリーンショット_2019-06-24_23_37_50.jpg

[Continue]をタップします。
スクリーンショット_2019-06-24_23_39_26.jpg

Microsoftアカウントを入力し、[Next]をタップします。
スクリーンショット_2019-06-24_23_41_28.jpg

パスワードを入力し、[Sign in]をタップします。
スクリーンショット_2019-06-24_23_43_20.jpg

[Result from Graph:]にJSONデータが表示されたらログイン成功です!
スクリーンショット_2019-06-24_23_45_04.jpg

予定表の取得

自分の1週間の予定表を取得する処理を実装します。

GraphエクスプローラーにサインインしてAPIを試し、クエリやレスポンスボディを把握すると実装しやすいです。
https://developer.microsoft.com/ja-jp/graph/graph-explorer

サンプルコードの getContentWithToken() メソッドをコピぺし、メソッド名とURLのみ変更すればOKです。

ViewController.swift
    private 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.swift
import 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()

予定表のデータがJSON形式で表示されたら成功です!
スクリーンショット_2019-06-25_0_32_06.jpg

おわりに

あとはこれらのコードを参考に製品アプリを実装すればOKです。

iOSアプリからMicrosoftのデータを取得するのはなかなか大変でした:sweat_smile:

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ダークモード適用を回避する方法

はじめに

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.plistUIUserInterfaceStyleパラメータを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のダークモードに関するセッション。動画やサンプルコードがあります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む