20200223のSwiftに関する記事は8件です。

Xcodeのシミュレーターの画面録画する方法

Xcodeのシミュレーターで画面録画する方法を学んだので、共有しておきます。

簡単に解説

  1. シミュレーターを起動する
  2. ターミナルでプロジェクトのディレクトリに行く
  3. xcrun simctl io booted recordVideo testMovie.movで撮影開始
  4. シミュレーター動かす
  5. control + cで撮影終了

丁寧に解説

スクリーンショット 2020-02-23 21.48.51 1.png
【Xcode/Swift5】Xcodeのシミュレーターの画面撮影(画面録画)する方法

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

XcodeGenでSwift Package Managerの依存関係を設定する

下記のドキュメントを参考にしました。
https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md#swift-package

環境

Xcode: 11.3
XcodeGen: 2.13.1

XcodeGenとは

Xcodeでのプロジェクトファイルをyamlファイルから生成するものです。
githubリポジトリはこちら
https://github.com/yonaskolb/XcodeGen

サンプルコード

project.yml
name: TestApp
packages:
  HogePackage:
    url: https://github.com/~~~
    version: x.x.x

targets:
  TestApp:
    dependencies:
      - package: HogePackage

依存パッケージをpackagesで宣言して、利用するtargetで依存関係を定義するということですね。

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

RSSについて

iOSアプリで自分好みのRSSリーダーが欲しくなり、作ろうと思ったのですがそもそもRSSとは何だっけ?ということになり1から調べ直しました。

RSSとは

ニュースやブログなど各種のウェブサイトの更新情報を配信するための文書フォーマットの総称である。
※Wikipediaより抜粋

また、RSSという名前自体にも複数の意味解釈がありました。

RDF Site Summary

ファイル拡張子が.rdfとなっているもの。全体がrdf要素(<rdf>~</rdf>)で囲まれていることからも分かります。系統としてはRSS0.9→1.0系に分類されます。

Really Simple Syndication

ファイル拡張子が.rssまたは.xmlとなっているもの。RDFとは異なる体型で発展していったものです。系統としてはRSS2.0系に分類されます。

ATOM

ファイル拡張子が.xmlとなっているもの。RSSと同じ目的で異なるデータ形式で記述されたものという解釈でいいと思います。ATOMもRSSとして総称されることが多いようです。

フィードリーダー

フィードリーダー(feed reader)、またはフィード・アグリゲーター(feed aggregator)は、
ブログやポッドキャスト、ニュースサイトなどが配信するフィードを取得・購読するための
アプリケーションソフトウェアの総称。フィードのファイルフォーマットとしてRSSが先行していたことから、
現在でも「RSSリーダー」と呼ばれることが多い。
※Wikipediaより抜粋

以上のことからRSSには様々なフォーマットがあるので、リーダーを作るためには様々なフォーマットに対応したParserを実装する必要がありそうでした。

iOSでのRSSリーダー

今まで調べたことから、これからiOSアプリにおいてRSSリーダーを作る場合にはRSS1.0, RSS2.0, ATOMそれぞれのデータフォーマットに対応したParserを作ってあげる必要がありそうです。
自分で一から作るのもいいですが、車輪の再発明をしてもしょうがないなと思い、ライブラリがないか探してみました。するとやはりすでによさげなものがありました。

FeedKit

https://github.com/nmdias/FeedKit
RSS, ATOM, JSONに対応したFeed Parserです。
このライブラリの中身を読んでいて驚いたのが、データ取得をするのにURL Sessionなどの一般的なデータ転送処理が一切書かれていませんでした。
データの取得には

data = try Data(contentsOf: sanitizedSchemeUrl)

これだけです!賢い!

参考

https://ja.wikipedia.org/wiki/RSS
https://oshiete.goo.ne.jp/qa/3951173.html
https://www.nishishi.com/blog/2006/02/rss_rdf_xml.html
http://e-words.jp/w/RSS.html
http://e-words.jp/w/RSS%E3%83%95%E3%82%A3%E3%83%BC%E3%83%89.html

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

ARKitで2つのポイント間に線を引く

ARSCNViewやARViewで2つのポイント間に線を引きたいことがあります。
勿論一旦ピクセルにしてOpenCVといったライブラリで線を引くこともできますが、ここではオブジェクトを配置して線を引いてみます。

ARSCNView(SecneKit)だとSCNCylinder、ARView(RealityKit)だとgenerateBoxといったオブジェクトで擬似的に線に見立てると思います。
しかし、これはオブジェクトですので、線を引くのと違い、長さと回転をいい具合に調整する必要があります。
オブジェクトの回転ですが、Transform、EulerAngle、Quaternionのいずれかを用いて表します。
ただし、RealityKitでは直接EulerAngleを指定できませんのでTransformを使うか、Quaternionを用いる必要があります。
こういったピンポイントの処理を調べるのが面倒ですのでStack Overflowで調べた結果がこちらです。

EulerAngles

How to draw a line between two points in SceneKit?

let eulerAngles 
    = simd_float3(Float.pi / 2,
                  acos((to.z-from.z)/distance),
                  atan2((to.y-from.y),(to.x-from.x)))

SceneKitでは上記のEulerAngleの計算でうまく動作しました。
なお、RealityKitでは、ここでのEulerAngleの結果をTransformで初期化してもうまく動作しませんでした。

Transfrom(pitch: Float, yaw: Float, roll: Float)
Creates a new transform from the specified Euler angles.

Quaternion

Draw SceneKit object between two points

ここでは2017年3月のTransformの手法が良い回答となっていますが、良い回答からしばらく経った2019年の7月の誰も支持してないQuaternionの回答が良かったのでそれの引用になります。
勿論Transformの手法でも動作することを確認しましたが好みでなかったので割愛です。

手法ですが、長さを求めたうえで内積(cross)と外積(dot)を求めます。

let vector = to - from
let height = simd_length(vector)
let line_axis = simd_float3(0, height/2, 0)

let vector_cross = simd_cross(line_axis, vector)
let qw = simd_length(line_axis) * simd_length(vector) + simd_dot(line_axis, vector)
let q = simd_quatf(ix: vector_cross.x, iy: vector_cross.y,
                   iz: vector_cross.z, r: qw).normalized

平行ベクトルにおける問題

Finding quaternion representing the rotation from one vector to another

Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);

上のコードですと平行ベクトルの場合にハングするようですが、先ほどの線の長さを求める書き方だと一応問題なさそうです。
気づいた方いましたらお知らせください。

正しく制御したコードはEigenのFromTwoVectorsを参照するのが良さそうです。

Swift Playground

こちらにコードをあげました。
https://gist.github.com/otmb/9ab1c43774811ad8c5ff697f3538e762

出力すると球と球の間を繋ぐオブジェクトが配置でき、うまく動作しているようです。

スクリーンショット 2020-02-23 2.46.35.png

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

SwfitUIで通信中にIndicatorを表示できるようにする。

SwiftUIでLoadingの画面を表示したい!

先に現在できているコードを記載します。
また今回のコードのIndicator部分については、tsuzuki817さんの作りながら学ぶ! SwiftUIアニメーション インジケーター編を参考にさせていただきました。なのでIndicatorの実装に関する詳細は省略します。

struct LoadingIndicatorView: View {
    @Binding var isLoading: Bool
    @State private var isAnimating = false
    private let animation = Animation.linear(duration: 1).repeatForever(autoreverses: false)

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                // ①Loading中に画面をタップできないようにするためのほぼ透明なLayer
                Color(.black)
                    .opacity(0.01)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .edgesIgnoringSafeArea(.all)
                    .disabled(self.isLoading)
                Circle()
                    .trim(from: 0, to: 0.6)
                    .stroke(AngularGradient(gradient: Gradient(colors: [.gray, .white]), center: .center),
                            style: StrokeStyle(
                                lineWidth: 8,
                                lineCap: .round,
                                dash: [0.1, 16],
                                dashPhase: 8))
                    .frame(width: 48, height: 48)
                    .rotationEffect(.degrees(self.isAnimating ? 360 : 0))
                    // ②アニメーションの実装
                    .onAppear() {
                        withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                            self.isAnimating = true
                        }
                    }
                    .onDisappear() {
                        self.isAnimating = false
                    }
            }
             // ③Loading中だけLoading画面が表示されるようにする。
            .hidden(self.$isLoading.toggle())
        }
    }
}

使う時は、以下のように書きます。これでisLoadingの表示/非表示が切り替わります。

@State private var isLoading = false

var body: some View {
    ZStack {
        Text("Hello world!")
        LoadingIndicatorView(isLoading: self.$isLoading)
    }
}

以下のGifの画面では、真ん中の青いボタンをタップすると、3秒間だけisLoadingtrueになるような実装をしてあるため、3秒間だけLoading画面が出るようになっています。またLoading画面中が表示されている間は、青いボタンはタップできないようになっています。
20200223_023345.GIF

実装詳細

①Loading中に画面をタップできないようにするためのほぼ透明なLayer

Color(.black)
    .opacity(0.01)
    .frame(width: geometry.size.width, height: geometry.size.height)
    .edgesIgnoringSafeArea(.all)
    .disabled(self.isLoading)

なぜ「ほぼ」透明になっているかというと、Color(.clear)にしてしまうと言うとdisabledを指定しても、画面へのジェスチャーが効いてしますからです。これに気づくまで結構時間がかかりました。綺麗な書き方も思いつかなかったので、とりあえず黒いLayerを作成し、それに.opacity(0.01)を設定することで、限りなく透明で、ユーザーのジェスチャーを無効化するLayerを作成することになんとか成功しました。これはSwiftUIのバグではないのか、、、、ちなみに色を.clear以外に指定すると、ちゃんとdisabledが効きます。

②アニメーションの実装
参考資料では以下のように書かれていましたが、これだとLoadingのアニメーションが実行されるたびに、インジェクターが時計回りと反時計回り、交互に回転するようになってしました。

.onAppear() {
    withAnimation(
        Animation
            .linear(duration: 1)) {
                self.isAnimation.toggle()
    }

そこでViewが表示されるonAppear()isAnimationtrueに、非表示になる onDisappear()falseにする実装に変更しました。
これで何度Loading画面を表示しても、ずーと時計回りにIndicatorが回転しています。

.onAppear() {
    withAnimation(self.animation) {
        self.isAnimating = true
    }
}
.onDisappear() {
    self.isAnimating = false
}

③Loading中だけLoading画面が表示されるようにする。
親ViewのStateにBindしているisLoadingフラグに合わせて、Loading画面の表示/非表示が切り替わるような実装を行います。
該当箇所は以下の一行です。

.hidden(self.$isLoading.toggle())

hiddenは、カスタムのModifierです。詳細の実装については、こちらにまとめてあります。
問題はisLoadingの値をそのままhiddenに流すと、isLoadingtrueの時にLoading画面が非表示になってしまいます。そこで流れてくるbooleanの値を反転させる必要があります。RxSwiftで書くと、.map({ !$0 })のようなイメージです。そこでtoggle()というBindingのextensionを作成しました。作り方はこちらの記事を参考にしました。

import SwiftUI
import Combine

extension Binding where Value == Bool {
    func toggle() -> Binding<Bool> {
        Binding<Bool>(get: { () -> Bool in
            return !self.wrappedValue
        }, set: { value in
            self.wrappedValue = value
        })
    }
}

このように書くことで、カスタムのBindingを作成することができます。RxSwiftのカスタムオペレーターを作るようなイメージですね。
このカスタムのBindingを作成する方法は、他にも色々応用できそうです。

まとめ

SwiftUIは1つのファイルにまとめすぎると、かなり読みづらくなる印象があるので、カスタムのViewやModifier、extensionなどを作成して、コードを分割することで、可読性がかなり上がるなと思いました。
まだまだSwiftUIはUIKitと比較するとできることは限られていますが、これからはきっとSwiftUIが主流になっていくと思うので、SwiftUIのキャッチアップも進めていきたいですね。

参考資料

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

SwiftUIでRxSwiftのrx.isHidden的な書き方をできるようにする。

Bool値とViewの表示/非表示をBindできるようにしたい!

表題のことを実装したので、メモとして残しておきます。
RxSwiftでは以下のような書き方ができます。

private let booleanRelay = BehaviorRelay<Bool>(value: false)
// RxSwiftではこう書かける
booleanRelay
    .bind(to: label.rx.isHidden)
    .disposed(by: disposeBag)

しかしSwiftUIではこのように書く必要がある(ちがう書き方があったら、教えてください)

@State private var boolean = false
if boolean {
    Text("Hello world!")
}

この書き方だと以下の問題点があるように感じます。
1. ネストが深くなる。
2. 行数が増える。
3. 宣言的に書けない。

そこで以下のように書けるようにしてみました。

@State private var boolean = false
Text("Hello world!")
    .hidden($boolean)

これでbooleanの値に合わせて、Textの表示/非表示が切り替わるようになりました。

実装詳細

上記のhiddenは以下のように実装しています。

struct Hidden: ViewModifier {
    @Binding var hidden: Bool

    func body(content: Content) -> some View {
        VStack {
            if !hidden {
                content
            }
        }
    }
}

extension View {  
    func hidden(_ isHidden: Binding<Bool>) -> some View {
        ModifiedContent(content: self, modifier: Hidden(hidden: isHidden))
    }
}

最初の構造体の記述については、

struct Hidden: ViewModifier {
    // 親ViewのStateをBindできるように、Bindingをつける。
    @Binding var hidden: Bool

    func body(content: Content) -> some View {
        if !hidden {
            content
        }
    }
}

と書いてもいけるかなーと思ったのですが、bodyのとこでFunction declares an opaque return type, but has no return statements in its body from which to infer an underlying typeとエラーが出ました。いろいろとググりながら試行錯誤したら、上記のような実装になりました。内部実装を追えばわかるかもしれませんが、正直そこまでは追えていません。いつかはちゃんと理解しなければ、、、

まとめ

SwiftUIはまだ思った通りに書けないとこも多いので、そういったところはこのようなextensionを作って、うまく対応していければと思います。
また実装の仕方については、しっかり理解できていないところもあるので、少しずつ理解を深めていきたいなーと感じました。

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

SwiftUIのPreviewするデバイスをenumで選択できるようにしてみた。

SwiftUIでプレビューのデバイス指定はめんどう

SwiftUIでデバイスの種類を指定する時は、以下のように書くと思います。

struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
      Group {
         ContentView()
            .previewDevice(PreviewDevice(rawValue: "iPhone SE"))
            .previewDisplayName("iPhone SE")

         ContentView()
            .previewDevice(PreviewDevice(rawValue: "iPhone XS Max"))
            .previewDisplayName("iPhone XS Max")
      }
   }
}

しかし直接文字列で記述するのはタイポの可能性もありますし、そもそもなんて書けばいいんだっけ?と迷う→困る→ググる、といった不毛な時間を過ごすことになります。そこで以下のようにenumで選択できるようにしました。

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewDevice(device: .iPhone11Pro)
                .previewDisplayName(device: .iPhone11Pro)
            ContentView()
                .previewDevices(devices: [.iPhone11Pro, .iPadPro12_9inch])
        }
    }
}

これでタイポすることも、文字列として何を書けばいいのかと迷うこともありません。

実装の詳細

上記のコードを実現するには、カスタムのModifierを作成する必要があります。カスタムのModifierについてはこちらの記事が参考になりました。
以下、実装の詳細です。

①まずDeviceというenumを作ります。

enum Device: String {
    // iPhone
    case iPhoneSE = "iPhone SE"
    case iPhone7 = "iPhone 7"
    case iPhone7Plus = "iPhone 7 Plus"
    case iPhone8 = "iPhone 8"
    case iPhone8Plus = "iPhone 8 Plus"
    case iPhoneX = "iPhone X"
    case iPhoneXs = "iPhone Xs"
    case iPhoneXsMax = "iPhone Xs Max"
    case iPhoneXR = "iPhone XR"
    case iPhone11 = "iPhone 11"
    case iPhone11Pro = "iPhone 11 Pro"
    case iPhone11ProMax = "iPhone 11 Pro Max"
    // iPad
    case iPadMini4 = "iPad mini 4"
    case iPadAir2 = "iPad Air 2"
    case iPadPro9_7inch = "iPad Pro (9.7-inch)"
    case iPadPro12_9inch = "iPad Pro (12.9-inch)"
    case iPad5th = "iPad (5th generation)"
    case iPadPro12_9inch2nd = "iPad Pro (12.9-inch) (2nd generation)"
    case iPadPro10_5inch = "iPad Pro (10.5-inch)"
    case iPad6th = "iPad (6th generation)"
    case iPadPro11inch = "iPad Pro (11-inch)"
    case iPadPro12_9inch3rd = "iPad Pro (12.9-inch) (3rd generation)"
    case iPadMini5th = "iPad mini (5th generation)"
    case iPadAir3rd = "iPad Air (3rd generation)"
    // AppleTV
    case AppleTV = "Apple TV"
    case AppleTV4K = "Apple TV 4K"
    case AppleTV4K_1080p = "Apple TV 4K (at 1080p)"
    // AppleWatch
    case AppleWatchS2_38mm = "Apple Watch Series 2 - 38mm"
    case AppleWatchS2_42mm = "Apple Watch Series 2 - 42mm"
    case AppleWatchS3_38mm = "Apple Watch Series 3 - 38mm"
    case AppleWatchS3_42mm = "Apple Watch Series 3 - 42mm"
    case AppleWatchS4_40mm = "Apple Watch Series 4 - 40mm"
    case AppleWatchS4_44mm = "Apple Watch Series 4 - 44mm"
    case AppleWatchS5_40mm = "Apple Watch Series 5 - 40mm"
    case AppleWatchS5_44mm = "Apple Watch Series 5 - 44mm"
}

②カスタムのViewModifierを作成する。

/// 選択したデバイスのPreviewを表示する。
struct DisplayPreviewDevice: ViewModifier {
    let device: Device

    func body(content: Content) -> some View {
        content
            .previewDevice(PreviewDevice(rawValue: device.rawValue))
    }
}

// Arrayで選択したデバイスを全て表示する。
struct DisplayPreviewDevices: ViewModifier {
    let devices: [Device]

    func body(content: Content) -> some View {
        ForEach(devices, id: \.self) { device in
            content
                .previewDevice(PreviewDevice(rawValue: device.rawValue))
        }
    }
}

// 選択したデバイスのデバイス名を表示する。
struct PreviewDisplayDeviceName: ViewModifier {
    let device: Device

    func body(content: Content) -> some View {
        content
            .previewDisplayName(device.rawValue)
    }
}

③作成したViewModifierViewのextensiionに追加する。

extension View {
    func previewDevice(device: Device) -> some View {
        ModifiedContent(content: self, modifier: DisplayPreviewDevice(device: device))
    } 

    func previewDevices(devices: [Device]) -> some View {
        ModifiedContent(content: self, modifier: DisplayPreviewDevices(devices: devices))
    }

    func previewDisplayName(device: Device) -> some View {
        ModifiedContent(content: self, modifier: PreviewDisplayDeviceName(device: device))
    }
}

これでPreviewも簡単に表示できるようになりました☺️

おまけ

デバイスのLight/Darkモードを指定して表示したいこともあると思います。ついでにこちらも簡単に指定できるModifierを作成しました。

enum ColorMode {
    case light
    case dark
}

struct ColorScheme: ViewModifier {
    let color: ColorMode

    func body(content: Content) -> some View {
        switch color {
        case .light:
            return content
                .environment(\.colorScheme, .light)
        case .dark:
            return content
                .environment(\.colorScheme, .dark)
        }

    }
}

extension View {
    func colorScheme(color: ColorMode) -> some View {
        ModifiedContent(content: self, modifier: ColorScheme(color: color))
    }
}

使用例

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewDevice(device: .iPhone11Pro)
                .previewDisplayName(device: .iPhone11Pro)
                // ホントはこんな感じで書く
                .environment(\.colorScheme, .dark)
            ContentView()
                .previewDevices(devices: [.iPhone11Pro, .iPadPro12_9inch])
                // enumで指定するだけで書ける!
                .colorScheme(.dark)
        }
    }
}

まとめ

enumで指定できるようにすることで、Previewを表示しすくなりました。
またPreviewを複数表示したい時もあると思うので、それを一つのメソッドで指定できるのは便利だと思いました。
ただpreviewDevicesを使用すると、iPhoneはライトモード、iPadはダークモードなど個別に設定を指定できないので、単純に複数デバイスでUIが崩れていないかを確認する場合にだけ使えそうです。
SwiftUIもこうやってどんどん使いやすくしていければなーと思います。

注意点

enumのDeviceでしていしたデバイスの一覧は全てPreviewで正常に表示されるかを確認していないので、エラーになる場合もあるかもしれません。その場合はこっそり教えていただけるとうれしいです。こちら公式です。

参考資料

- 【SwiftUI】カスタムModifierの作成
- Apple Developer - previewDevice(_:)

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

iOSアプリを初めて開発する人へ

iOSエンジニアになるために...

現場でSwiftを触って約1年半が経ちました。
今では一人でアプリを開発することができるようになり、個人でアプリを開発し公開しようと企んでいる最中です。
そんな私が初めてSwift触った時からお世話になったサイトをQiitaの記事をメインにピックアップしてみました。

1.環境構築

ツール関係

homebrew(Macのパッケージ管理ツール)

XCode

CocoaPods

Swift Package Manager

2.開発の基本

Swiftを全く触ったこと無い向け

Hello World !! 的なもの

Interface Builder

TableView

3.簡単なアプリを作ってみる

4.ライブラリを組み込んでアプリをリッチに

5.アプリを公開してみる

6.デプロイをもっと早く

最後に

とりあえずお世話になった記事を挙げてみました。思い出しながら書いたのでまだまだ漏れがあるかとおもいます。
まだまだ書きたいことが沢山あるので頻繁に更新していきます。

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