20201114のSwiftに関する記事は7件です。

Moya+RxSwiftのAPIクライアントの一例(Swift5)

前提

SwiftではデフォルトではURLSessionクラスで通信処理を行うが、Moyaはそれのラッパとなるライブラリである。

便利に書ける事から、開発の現場ではスタンダードなやり方と思われる。特にRxSwiftと組み合わせるとさらに便利。

一応、URLSessionクラスもある程度知っておいた方がいい事から、初心者の方はまずURLSessionで慣れた後、Moyaに移行し便利に使える事を実感した方がいいだろう。

公式
Swift4 + Moya + RxSwift + Codableで作るAPIクライアント

全体

APIClient.swift
APIClient.swift
final class APIClient {
    private init() {}
    static let shared = APIClient()

    // MARK: - Private

    private let provider = MoyaProvider<MultiTarget>()

    private let stubProvider = MoyaProvider<MultiTarget>(stubClosure: MoyaProvider.immediatelyStub)

    // MARK: - Public

    func request<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
        Single<G.Reponse>.create { observer in
            self.makeRequest(request)
                .subscribe(onSuccess: { response in
                    observer(.success(response))
                }, onError: { error in
                    //プロジェクト全体で共通して行いたいエラーハンドリング等
                    observer(.error(error))
                })
        }
    }

    func makeRequest<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
        provider.rx
            .request(MultiTarget(request))
            .flatMap({ response -> Single<Response> in
                // レスポンスヘッダーのチェック
                return Single.just(try response.lookingAllHeaderFields())
            })
            .flatMap { response -> Single<Response> in
                // エラーコードのチェック
                return Single.just(try response.successfulStatusCodesPolicy())
        }
        .map(G.Reponse.self, failsOnEmptyData: false)
    }

    func requestStub<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
        Single<G.Reponse>.create { observer in
            self.makeRequestStub(request)
                .subscribe(onSuccess: { response in
                    observer(.success(response))
                }, onError: { error in
                    if let error = error as? MoyaError {
                    //プロジェクト全体で共通して行いたいエラーハンドリング等
                    observer(.error(error))
                })
        }
    }

    func makeRequestStub<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
        stubProvider.rx
            .request(MultiTarget(request))
            .flatMap({ response -> Single<Response> in
                // レスポンスヘッダーのチェック
                return Single.just(try response.lookingAllHeaderFields())
            })
            .flatMap { response -> Single<Response> in
                // エラーコードのチェック
                return Single.just(try response.successfulStatusCodesPolicy())
        }
        .map(G.Reponse.self, failsOnEmptyData: false)
    }
}

ApiTargetType.swift
ApiTargetType.swift
import Foundation
import Moya

protocol ApiTargetType: TargetType {
    associatedtype Response: Codable
}

extension ApiTargetType {

    /// The target's base `URL`.
    var baseURL: URL {
        URL(string: "\(URLs.baseURL)")!
    }
}

解説

APIクライアントはシングルトンオブジェクト
  • API通信を行うため、MoyaProviderクラスのオブジェクトが通信途中で解放されてしまうなどという事態は避けなくてはならない。APIクライアントを利用する側で、MoyaProviderクラスのオブジェクトを(メンバ変数として設定するなどして)十分長期間保持するということもできる。ここではそのような手間を避けるため、シングルトンオブジェクトとしてsharedを設定し、MoyaProviderがプログラムの実行の全過程を通じて存続するようにしている。
APIClient.swift
final class APIClient {
    private init() {}
    static let shared = APIClient()
MultiTargetの使用
  • 異なる種類のTargetTypeをMoyaProviderに割り当てられるようにするため、MultiTargetを利用している。これにより、APIクライアント利用側ではジェネリクスを通じてMoyaProviderにTargetTypeを渡すという手間が省け、MoyaProviderの存在を利用側に対して隠蔽することができる。
APIClient.swift
private let provider = MoyaProvider<MultiTarget>()
Pluginの利用
  • 必要であれば、MoyaProviderを初期化する際、Pluginというものを渡す事ができる。これはリクエストを送る前、またはレスポンスを受け取った後などに何か副作用を記載したい場合に利用できる。用途としては、公式のコメントに記載があるように、リクエストを送る・レスポンスを受け取るときにログを出すであるとか、インジケータ(ローディング中である事を示すグルグル回るもの)の出し入れを行うとか、リクエストを送る前にURLRequestの設定を変更したい時などに利用できる。
APIClient.swift
private let provider = MoyaProvider<MultiTarget>(plugins: [FooPlugin()])
Plugin.swift
/// A Moya Plugin receives callbacks to perform side effects wherever a request is sent or received.
///
/// for example, a plugin may be used to
///     - log network requests
///     - hide and show a network activity indicator
///     - inject additional information into a request
public protocol PluginType {
    /// Called to modify a request before sending.
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest

    /// Called immediately before a request is sent over the network (or stubbed).
    func willSend(_ request: RequestType, target: TargetType)

    /// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
    func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)

    /// Called to modify a result before completion.
    func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
}
stubProviderの利用
  • stubProviderを使うと、各TargetTypeに記載してあるサンプルデータをレスポンスとして返す事ができる。スタブの振る舞いとしては.immediatelyStubだと即座にスタブデータを返すが、.delayだと指定した秒数後にデータを返す事ができ、より本番に近い状況でテストしたい時には有効。
APIClient.swift
    private let stubbingProvider = MoyaProvider<MultiTarget>(stubClosure: MoyaProvider.immediatelyStub) //.delayed(seconds: 1.0)等でも可
レスポンスのチェック
  • 返ってきたレスポンスにはステータスコード・ヘッダーフィールド・データが含まれているが、これらに対して行いたい処理がプロジェクト全体で共通している場合は、APIクライアントに記載してしまうと良い。エラーハンドリングや、ログを出す等。
APIClient.swift
    // MARK: - Public

    func request<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
        Single<G.Reponse>.create { observer in
            self.makeRequest(request)
                .subscribe(onSuccess: { response in
                    observer(.success(response))
                }, onError: { error in
                    //プロジェクト全体で共通して行いたいエラーハンドリング等
                    observer(.error(error))
                })
        }
    }

    func makeRequest<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
        provider.rx
            .request(MultiTarget(request))
            .flatMap({ response -> Single<Response> in
                // レスポンスヘッダーのチェック
                return Single.just(try response.lookingAllHeaderFields())
            })
            .flatMap { response -> Single<Response> in
                // エラーコードのチェック
                return Single.just(try response.successfulStatusCodesPolicy())
        }
        .map(G.Reponse.self, failsOnEmptyData: false)
    }

ターゲットタイプ

プロジェクト全体で共通するもの(典型的には、ベースのURLなど)があれば、くくり出してTargetTypeのプロトコルを作ると良い。

protocol ApiTargetType: TargetType {
    associatedtype Response: Codable
}

extension ApiTargetType {

    /// The target's base `URL`.
    var baseURL: URL {
        URL(string: "\(URLs.baseURL)")!
    }
}

利用例

    func fetchData() -> Single<HogeEntity> {
        return APIClient.shared.request(HogeTargetType())
    }
HogeTargetType.swift
import Foundation
import Moya

struct HogeTargetType: ApiTargetType {
    typealias Response = HogeEntity

    var path: String {
        return "/hogehoge/"
    }

    var method: Moya.Method {
        return .get
    }
    var task: Task {
        let param: [String: Any] = [
            "id": id
        ]

        return .requestParameters(parameters: param, encoding: URLEncoding.default)
    }
    var headers: [String: String]? {
        [
            "Content-Type": "application/json",
            "x-auth-token": API.authToken
        ]
    }

       var sampleData: Data {
        let path = Bundle.main.path(forResource: "Hoge", ofType: "json")!
        guard let file = FileHandle(forReadingAtPath: path) else {
            return Data()
        }

        return file.readDataToEndOfFile()
    }

    // MARK: - Arguments

    /// ID
    var id: String {
        return DataManager.shared.id ?? ""
    }

    init() {}
}

HogeEntity.swift
struct HogeEntity: Codable {
   let fuga: String
Hoge.json
{
   "fuga": "test"
}

参考資料

Swift4 + Moya + RxSwift + Codableで作るAPIクライアント
Moya/Examples/Multi-Target/ViewController.swift

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

【備忘録】iOS 14 細かいTips 写真アクセス権限・バックボタン

iOS 14のTipsを書いていこうと思います。

写真のアクセス権限の変更

iOS 13までは写真のアクセス権限の選択肢が許可しないOKの2択でしたが、
iOS 14からは写真のアクセス権限の選択肢が 写真を選択全ての写真へのアクセスを許可許可しないの3択になりました。
iOS 14では写真個別にアクセス権限を与えれるようになりました。

iOS 13ダイアログ
Screen Shot 2020-11-14 at 2.11.14.png

iOS 14ダイアログ

Screen Shot 2020-11-14 at 2.14.44.png

写真のアクセス権限の呼び出しは以下のコードでOSに合わせてダイアログを表示します。

PHPhotoLibrary.requestAuthorization { _ in
   // 省略
}

iOS 14でも従来のダイアログを出すには?

アプリで写真を保存するだけの用途なら従来のダイアログで問題ないと思います。

まず、Info.plistにPrivacy - Photo Library Additions Usage Description (NSPhotoLibraryAddUsageDescription)を設定して文言を追加します。

スクリーンショット 2020-11-14 2.38.57.png

なら以下のコードで大丈夫です。

if #available(iOS 14, *) {
     PHPhotoLibrary.requestAuthorization(for: .addOnly) { _ in
        // 省略     
     }
} else {
     PHPhotoLibrary.requestAuthorization { _ in
        // 省略        
     }
}

requestAuthorizationというメソッドで 写真のみ追加もしくは 読み出し/書き込みのどちらかで指定して呼び出しできるので .addOnlyを指定すると従来のダイアログが出すことができます。

Screen Shot 2020-11-14 at 2.40.21.png

バックボタン

iOS 14からバックボタンを長押しすることで前の画面らをリストしたポップアップが表示され、一気に一番最初の画面に戻るなどできるようになりました。

例 設定アプリ
Screen Shot 2020-11-14 at 15.09.03.png

他にもバックボタンの見た目も制御できるようになり、UINavigationItemクラスに以下のプロパティが追加されました。

var backButtonDisplayMode: UINavigationItem.BackButtonDisplayMode { get set }

見た目は3種類設定できます

.default

前の画面タイトルがバックボタンに反映します
※タイトルが入ってなければ Backになります

Screen Shot 2020-11-14 at 15.26.00.png

.generic

バックボタンがBackになります
Screen Shot 2020-11-14 at 15.29.55.png

.minimal

バックボタンに名称が表示されず<だけになります
Screen Shot 2020-11-14 at 15.30.56.png

コード例

if #available(iOS 14.0, *) {
    self.navigationItem.backButtonDisplayMode = .minimal
}

注意

バックボタンの見た目は制御できますが、長押しした際のリストは画面タイトル(self.title)もしくはバックボタン(backButtonTitle)の文言が表示されます。

もし何も文言を入れてないと空欄にまたは同じ文言を入れると、どこの画面に戻るかぱっと見でわからなくなってしまいます。

例 同じ文言
Screen Shot 2020-11-14 at 15.37.57.png

iOS 14のバックボタンの対応として長押しした際にどこの画面に戻るか分かる文言を設定してあげるといいです。

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

RxSwift ボタン連打防止

throttleOperatorを用いると良い。

throttle及び似たような機能のあるdebounceについてはこちらを参照。
RxSwiftのDebounceとThrottle
throttleはボタン連打防止、debounceはいわゆるインクリメンタルサーチなどAPI呼びすぎ防止などで使える。

実際には以下のようなextensionを作ると便利である。

Reactive+Extenstions.swift
import RxCocoa
import RxSwift

public extension Reactive where Base: UIButton {
    var throttledTap: ControlEvent<Void> {
        return ControlEvent<Void>(events: tap
            .throttle(.milliseconds(ContinuousTap.disableTapDuration), latest: false, scheduler: MainScheduler.instance))
    }
}

public extension Reactive where Base: UIBarButtonItem {
    var throttledTap: ControlEvent<()> {
        return ControlEvent<()>(events: tap
            .throttle(.milliseconds(ContinuousTap.disableTapDuration), latest: false, scheduler: MainScheduler.instance))
    }
}

public extension Reactive where Base: UITableView {
    var throttledItemSelected: ControlEvent<IndexPath> {
        return ControlEvent<IndexPath>(events: itemSelected
            .throttle(.milliseconds(ContinuousTap.disableTapDuration), latest: false, scheduler: MainScheduler.instance) )
    }
}

public extension Reactive where Base: UICollectionView {
    var throttledItemSelected: ControlEvent<IndexPath> {
        return ControlEvent<IndexPath>(events: itemSelected
            .throttle(.milliseconds(ContinuousTap.disableTapDuration), latest: false, scheduler: MainScheduler.instance) )
    }
}

enum ContinuousTap {
    /// Disable Tap Duration in Milliseconds
    static let disableTapDuration: Int = 500
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

scrollviewで可変サイズUIViewがタップできなくてハマった

ContainerVCを用いて、
ボタンを押すと大きさが可変となるUIViewを作成していました。
このUIViewにはtapgestureRecognizer あるいは touchesbeganで
タップを検出できるようにしていました。
(参考 https://qiita.com/Kyome/items/d86cefa9dbd7bd2d7cf0)

ボタンが押されたときに

a.swift
@IBOutlet weak var containerVCWidth: NSLayoutConstraint!
  self.myScrollView.contentSize.width += 100
  self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.width + 100, height: self.view.frame.height)
}

としていましたが、viewは大きくなるのにタップできない現象が発生しました。

解決策

containerViewのwidthに関する制約を
outlet接続して、それもインクリメントする必要があったようです。

a.swift
@IBOutlet weak var containerVCWidth: NSLayoutConstraint!

@IBAction func tappedButton(_ sender: Any) {
  self.myScrollView.contentSize.width += 100
  containerVCWidth.constant += 100
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUI 他のフレームワークを組み合わせると使えるView

SwiftUIの他に各種フレームワークをimportすると使えるViewやModifierです。

MapKit

Mapが使えます。

サンプルコード

import SwiftUI
import MapKit

struct SwiftUIView: View {
    @State var region: MKCoordinateRegion
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        let initialCoordinate = CLLocationCoordinate2DMake(40,40)
        let span = MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
        let region = MKCoordinateRegion(center: initialCoordinate, span: span)

        return SwiftUIView(region: region)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.34.52.png

SpriteKit

SpriteViewが使えます。
GameSceneファイルはXcodeプロジェクトをMultiplatformで作成した際のものを使っています。

サンプルコード

import SwiftUI
import SpriteKit

struct SwiftUIView: View {
    let scene: SKScene
    var body: some View {
        SpriteView(scene: scene)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        guard let scene = SKScene(fileNamed: "GameScene") as? GameScene else {
            abort()
        }
        scene.scaleMode = .aspectFit

        return SwiftUIView(scene: scene)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.36.21.png

AuthenticationServices

SignInWithAppleButtonが使えます。

サンプルコード

import SwiftUI
import AuthenticationServices

struct SwiftUIView: View {
    var body: some View {
        SignInWithAppleButton(.continue) { _ in

        } onCompletion: { _ in

        }
        .frame(width: 300.0, height: 44.0)
        .signInWithAppleButtonStyle(.black)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.54.47.png

StoreKit

appStoreOverlayが使えます。

サンプルコード

import SwiftUI
import StoreKit

struct SwiftUIView: View {
    @State var showOverlay:Bool = false
    var body: some View {
        Button("App Store Overlay") {
            self.showOverlay.toggle()
        }
        .appStoreOverlay(isPresented: $showOverlay) {
            SKOverlay.AppConfiguration(appIdentifier: "687721425", position: .bottom)
        }
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        return SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.46.41.png

AVKit

VideoPlayerが使えます。
ドキュメントには A view that displays the video content from a player object along with system-supplied playback controls. とあるんですが、tvOSだとコントロールが出ない感じです。

サンプルコード

import SwiftUI
import AVKit

struct SwiftUIView: View {
    let player: AVPlayer?
    var body: some View {
        VideoPlayer(player: player)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        return SwiftUIView(player: AVPlayer(url: URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")!))
    }
}

プレビュー

スクリーンショット 2020-11-14 11.34.07.png

SceneKit

SceneView が使えます。scnファイルは各自用意してください。

サンプルコード

import SwiftUI
import SceneKit

struct SwiftUIView: View {
    let scene: SCNScene?
    var body: some View {
        SceneView(scene: scene)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        let scene = SCNScene(named: "Scene.scn")

        return SwiftUIView(scene: scene)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.32.49.png

HomeKit

CameraViewが使えます。
HMCameraSource が必要になりますが、おそらくSwiftUIプレビューでは用意できないので、各自実機でご確認ください。

サンプルコード

import SwiftUI
import HomeKit

struct SwiftUIView: View {
    let cameraSource:HMCameraSource
    var body: some View {
        CameraView(source: cameraSource)
    }
}

画像

IMG_1F757207E2D1-1.jpeg

WatchKit

NowPlayingViewが使えます。

サンプルコード

import SwiftUI
import WatchKit

struct SwiftUIView: View {
    var body: some View {
        NowPlayingView()
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.39.10.png

まとめ

つかおう!HomeKit!

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

SwiftUIを他のフレームワークと組み合わせる

SwiftUIの他に各種フレームワークをimportすると使えるViewやModifierです。

MapKit

Mapが使えます。

サンプルコード

import SwiftUI
import MapKit

struct SwiftUIView: View {
    @State var region: MKCoordinateRegion
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        let initialCoordinate = CLLocationCoordinate2DMake(40,40)
        let span = MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
        let region = MKCoordinateRegion(center: initialCoordinate, span: span)

        return SwiftUIView(region: region)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.34.52.png

SpriteKit

SpriteViewが使えます。
GameSceneファイルはXcodeプロジェクトをMultiplatformで作成した際のものを使っています。

サンプルコード

import SwiftUI
import SpriteKit

struct SwiftUIView: View {
    let scene: SKScene
    var body: some View {
        SpriteView(scene: scene)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        guard let scene = SKScene(fileNamed: "GameScene") as? GameScene else {
            abort()
        }
        scene.scaleMode = .aspectFit

        return SwiftUIView(scene: scene)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.36.21.png

AuthenticationServices

SignInWithAppleButtonが使えます。

サンプルコード

import SwiftUI
import AuthenticationServices

struct SwiftUIView: View {
    var body: some View {
        SignInWithAppleButton(.continue) { _ in

        } onCompletion: { _ in

        }
        .frame(width: 300.0, height: 44.0)
        .signInWithAppleButtonStyle(.black)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.54.47.png

StoreKit

appStoreOverlayが使えます。

サンプルコード

import SwiftUI
import StoreKit

struct SwiftUIView: View {
    @State var showOverlay:Bool = false
    var body: some View {
        Button("App Store Overlay") {
            self.showOverlay.toggle()
        }
        .appStoreOverlay(isPresented: $showOverlay) {
            SKOverlay.AppConfiguration(appIdentifier: "687721425", position: .bottom)
        }
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        return SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.46.41.png

AVKit

VideoPlayerが使えます。
ドキュメントには A view that displays the video content from a player object along with system-supplied playback controls. とあるんですが、tvOSだとコントロールが出ない感じです。

サンプルコード

import SwiftUI
import AVKit

struct SwiftUIView: View {
    let player: AVPlayer?
    var body: some View {
        VideoPlayer(player: player)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        return SwiftUIView(player: AVPlayer(url: URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")!))
    }
}

プレビュー

スクリーンショット 2020-11-14 11.34.07.png

SceneKit

SceneView が使えます。scnファイルは各自用意してください。

サンプルコード

import SwiftUI
import SceneKit

struct SwiftUIView: View {
    let scene: SCNScene?
    var body: some View {
        SceneView(scene: scene)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        let scene = SCNScene(named: "Scene.scn")

        return SwiftUIView(scene: scene)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.32.49.png

HomeKit

CameraViewが使えます。
HMCameraSource が必要になりますが、おそらくSwiftUIプレビューでは用意できないので、各自実機でご確認ください。

サンプルコード

import SwiftUI
import HomeKit

struct SwiftUIView: View {
    let cameraSource:HMCameraSource
    var body: some View {
        CameraView(source: cameraSource)
    }
}

画像

IMG_1F757207E2D1-1.jpeg

WatchKit

NowPlayingViewが使えます。

サンプルコード

import SwiftUI
import WatchKit

struct SwiftUIView: View {
    var body: some View {
        NowPlayingView()
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.39.10.png

まとめ

つかおう!HomeKit!

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

SwiftUIを他のフレームワークと組み合わせると使えるView

SwiftUIの他に各種フレームワークをimportすると使えるViewやModifierです。

MapKit

Mapが使えます。

サンプルコード

import SwiftUI
import MapKit

struct SwiftUIView: View {
    @State var region: MKCoordinateRegion
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        let initialCoordinate = CLLocationCoordinate2DMake(40,40)
        let span = MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
        let region = MKCoordinateRegion(center: initialCoordinate, span: span)

        return SwiftUIView(region: region)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.34.52.png

SpriteKit

SpriteViewが使えます。
GameSceneファイルはXcodeプロジェクトをMultiplatformで作成した際のものを使っています。

サンプルコード

import SwiftUI
import SpriteKit

struct SwiftUIView: View {
    let scene: SKScene
    var body: some View {
        SpriteView(scene: scene)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        guard let scene = SKScene(fileNamed: "GameScene") as? GameScene else {
            abort()
        }
        scene.scaleMode = .aspectFit

        return SwiftUIView(scene: scene)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.36.21.png

AuthenticationServices

SignInWithAppleButtonが使えます。

サンプルコード

import SwiftUI
import AuthenticationServices

struct SwiftUIView: View {
    var body: some View {
        SignInWithAppleButton(.continue) { _ in

        } onCompletion: { _ in

        }
        .frame(width: 300.0, height: 44.0)
        .signInWithAppleButtonStyle(.black)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.54.47.png

StoreKit

appStoreOverlayが使えます。

サンプルコード

import SwiftUI
import StoreKit

struct SwiftUIView: View {
    @State var showOverlay:Bool = false
    var body: some View {
        Button("App Store Overlay") {
            self.showOverlay.toggle()
        }
        .appStoreOverlay(isPresented: $showOverlay) {
            SKOverlay.AppConfiguration(appIdentifier: "687721425", position: .bottom)
        }
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        return SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.46.41.png

AVKit

VideoPlayerが使えます。
ドキュメントには A view that displays the video content from a player object along with system-supplied playback controls. とあるんですが、tvOSだとコントロールが出ない感じです。

サンプルコード

import SwiftUI
import AVKit

struct SwiftUIView: View {
    let player: AVPlayer?
    var body: some View {
        VideoPlayer(player: player)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        return SwiftUIView(player: AVPlayer(url: URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")!))
    }
}

プレビュー

スクリーンショット 2020-11-14 11.34.07.png

SceneKit

SceneView が使えます。scnファイルは各自用意してください。

サンプルコード

import SwiftUI
import SceneKit

struct SwiftUIView: View {
    let scene: SCNScene?
    var body: some View {
        SceneView(scene: scene)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        let scene = SCNScene(named: "Scene.scn")

        return SwiftUIView(scene: scene)
    }
}

プレビュー

スクリーンショット 2020-11-14 11.32.49.png

HomeKit

CameraViewが使えます。
HMCameraSource が必要になりますが、おそらくSwiftUIプレビューでは用意できないので、各自実機でご確認ください。

サンプルコード

import SwiftUI
import HomeKit

struct SwiftUIView: View {
    let cameraSource:HMCameraSource
    var body: some View {
        CameraView(source: cameraSource)
    }
}

画像

IMG_1F757207E2D1-1.jpeg

WatchKit

NowPlayingViewが使えます。

サンプルコード

import SwiftUI
import WatchKit

struct SwiftUIView: View {
    var body: some View {
        NowPlayingView()
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView()
    }
}

プレビュー

スクリーンショット 2020-11-14 11.39.10.png

まとめ

つかおう!HomeKit!

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