- 投稿日:2020-02-23T21:50:05+09:00
Xcodeのシミュレーターの画面録画する方法
- 投稿日:2020-02-23T16:45:03+09:00
XcodeGenでSwift Package Managerの依存関係を設定する
下記のドキュメントを参考にしました。
https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md#swift-package環境
Xcode: 11.3
XcodeGen: 2.13.1XcodeGenとは
Xcodeでのプロジェクトファイルをyamlファイルから生成するものです。
githubリポジトリはこちら
https://github.com/yonaskolb/XcodeGenサンプルコード
project.ymlname: TestApp packages: HogePackage: url: https://github.com/~~~ version: x.x.x targets: TestApp: dependencies: - package: HogePackage依存パッケージをpackagesで宣言して、利用するtargetで依存関係を定義するということですね。
- 投稿日:2020-02-23T11:43:58+09:00
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
- 投稿日:2020-02-23T03:49:37+09:00
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-23T02:37:43+09:00
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秒間だけ
isLoading
がtrue
になるような実装をしてあるため、3秒間だけLoading画面が出るようになっています。またLoading画面中が表示されている間は、青いボタンはタップできないようになっています。
実装詳細
①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()
でisAnimation
をtrue
に、非表示になる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
に流すと、isLoading
がtrue
の時に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のキャッチアップも進めていきたいですね。参考資料
- 投稿日:2020-02-23T01:26:32+09:00
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を作って、うまく対応していければと思います。
また実装の仕方については、しっかり理解できていないところもあるので、少しずつ理解を深めていきたいなーと感じました。
- 投稿日:2020-02-23T00:44:46+09:00
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) } }③作成した
ViewModifier
をView
の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(_:)
- 投稿日:2020-02-23T00:02:57+09:00
iOSアプリを初めて開発する人へ
iOSエンジニアになるために...
現場でSwiftを触って約1年半が経ちました。
今では一人でアプリを開発することができるようになり、個人でアプリを開発し公開しようと企んでいる最中です。
そんな私が初めてSwift触った時からお世話になったサイトをQiitaの記事をメインにピックアップしてみました。1.環境構築
ツール関係
homebrew(Macのパッケージ管理ツール)
- 公式サイト
- homebrewとは何者か。仕組みについて調べてみた
- homebrewについて細かく説明してくれてます。
XCode
- 今さら聞けない!Xcodeをインストールする方法【iOSアプリの統合開発環境】
- 記事は古いですがインストール方法が載っています。
- Xcodeの使い方を5分で理解!画像アプリの開発で即実践!(外部)
- 基本的な使い方はある程度マスターできます。
CocoaPods
- 公式サイト
- iOSライブラリ管理ツール「CocoaPods」の使用方法」(外部)
- CocoaPodsについて1から教えてくれます。
- CocoaPodsのインストール方法(外部)
- homebrew経由でのインストール方法が説明されています。
Swift Package Manager
- Xcodeに組み込まれているので導入しやすいかも。
- iOSアプリ開発にSwift Package Managerを使おう
- CocoaPodsより最近人気のSwift Package Managerについて詳しく書かれています。
2.開発の基本
Swiftを全く触ったこと無い向け
- [Swift]他言語使用者のためのSwift入門知識まとめ
- プログラミング経験者ならこれを軽く読めば、どんな言語かわかるはず。
- [Swift] iOSアプリ開発の基本情報と学習ソース
- 開発ツールやiOSのアプリに必要な環境などが記述されている。
- どこよりも分かりやすいSwiftの"?"と"!"
- 誰もが苦戦するアンラップなどがこれを読めば理解できます。
Hello World !! 的なもの
- 【iphoneアプリ開発】swiftでiosアプリを作ってみた!Hello Worldチュートリアル
- 私もこれで初めて実装してみました。
Interface Builder
- StoryboardやXibのこと。
- 初めてiPhoneアプリをデザインするには、どうすればいい?――すぐ分かるInterface Builder、Storyboard、Auto Layoutの使い方
- これで簡単なアプリは作れるようになる。
- 【Xcode/Swift】View Controllerと制約の使い方と理解
- これで制約ってどんなものかわかるはず
TableView
- これを使えるようになれば自分もアプリ作れそうな気がしてくるはず...
- SwiftでTableViewを使ってみよう
- 画像もいっぱいあって分かりやすいです。
- 【swift】イラストで分かる!具体的なDelegateの使い方。
- 誰もが苦戦する
Delegate
についてわかりやすく説明してくれてます。- 私をこれを読むまで理解できなかった...
3.簡単なアプリを作ってみる
- Swiftで簡単なTODOアプリを作ってみよう
- 手順も細かくて
いいね
も多いです。4.ライブラリを組み込んでアプリをリッチに
- CocoaPodsやSwift Package Managerをつかってリッチなアプリを作ってみよう。
- [初心者向け]SwiftでもObj-cでも、CocoaPodsを使ってiOSライブラリを使って開発効率をアップさせる
- 2018年これだけは知っておきたいiOSライブラリ31選
- 【Swift】iOSアプリ開発で使える(使いたい)Swiftライブラリー
- SwiftUIで使えそうなライブラリ!!
5.アプリを公開してみる
6.デプロイをもっと早く
最後に
とりあえずお世話になった記事を挙げてみました。思い出しながら書いたのでまだまだ漏れがあるかとおもいます。
まだまだ書きたいことが沢山あるので頻繁に更新していきます。