20220114のiOSに関する記事は5件です。

DMMのAPIを使ってSwiftUIでセクシー女優検索アプリを作る(18禁)

はじめに DMM Web APIを使ってSwiftUIでセクシー女優検察アプリを作成します。 中身は結構マジメにMVVMデザインパターンで作成しますが、内容が内容なので残念ながら18禁です。ごめんね。 今回はただ女優を表示するだけの簡単なアプリを作ります。 ちなみに僕は独学デベロッパーなので、細かいアーキテクチャとかわかっていないです。 表現に間違いがあるかと思いますがご容赦ください。 準備するもの 1 DMMアフィリエイト でWEBサービスの利用登録をします。アフィリエイトIDの承認には2〜3日かかリます。 2 APIの解析ツールを入手します。おすすめはTalend API Tester です。 3 SwiftUIなので当然Xcodeが必要です。 4 エロい気持ちはモチベーションを加速させます。 1 URL生成クラス 女優検索APIリファレンスに基づいてURLを作成するクラスを作成します。なお、いろんなパラメータがありますが、今回は最低限(セクシー女優若い順100人)のURLを作リます。 UrlManager.swift UrlManager.swift import Foundation class UrlManager{ var urlString:String = "" init(){ self.urlString = createUrl() } func createUrl()->String{ var requestParameters = Dictionary<String,String>() requestParameters["api_id"] = "自分のAPI ID" requestParameters["affiliate_id"] = "自分のアフィリエイトID" requestParameters["output"] = "json"//出力はJSON requestParameters["hits"] = "100"//表示数100件 requestParameters["sort"] = "-birthday"//若い人順 //URLを生成 var urlString = "https://api.dmm.com/affiliate/v3/ActressSearch?" urlString += requestParameters.map { (key: String, value: String) in key + "=" + value }.joined(separator: "&") return urlString } } これで生成されたURLをTalend API Tester に入力してみます。 うまくいきました! セクシー女優100人のデータも表示されています。 試しにパラメータを変えたり、増やしたりしていろいろ試してみることをお勧めします。 パラメータや出力データに関してはこちらをご確認ください。 そしてじっくりとどんなデータが吐き出されているか観察してみてください。 例えば上の画像のデータであればスリーサイズがnull(nil)だったりして意外とオプショナルなケースがあることがわかります。 このデータを解析していきます。 ちなみに僕のおすすめは「天音ゆい」です。 2 MODEL まずはモデルを作ります。 吐き出されるjsonはかなり大きいですが、今回使用するのはこれ(idと名前と画像データ)だけです。 これに合わせてstructを作るとこんな感じです。 画像データがない女優もいるので画像データ(imageURL)はオプショナルです。 swift:ActressModel ActressModel.swift import Foundation struct ActressModel:Codable{ let result:Result } struct Result:Codable{ let actress:[Actress] } struct Actress:Codable{ let id:String let name:String let imageURL:ImageURL? } struct ImageURL:Codable{ let large:String } なお、デコードするので、Codableプロトコルを記入しておきます。 3 VIEW とりあえず、VIEWは名前が表示されるのみです。(後で画像も表示されるよう改造します。) ちょっとトリッキーなところとしては各女優のid(文字列)をそのままVIEWのidとして使っているところくらいです。 ActressView.swift ActressView.swift import SwiftUI struct ActressView:View,Identifiable{ var id:String var actress:Actress init(actress:Actress){ self.actress = actress self.id = actress.id } var body: some View{ HStack{ //ここに画像表示VIEWを後で追加します。「7 ImageView」が完成したらコメントアウト解除 //ImageView(imageUrl: self.actress.imageURL?.large) Text(self.actress.name) } } } 4 VIEWMODEL ViewModelはURLから得られたデータをActressViewに変換し、 actressViews:[ActressView] に流し込みます。 今まで作った URL生成クラス、MODEL、VIEWをつなげる感じです。 この actressViews がContentView とバインディングします。 ということで、ObservableObject になります。 また、定番の方法として URLSession.shared.dataTask(with: url) {}.resume() と、非同期通信 DispatchQueue.main.async {} を使用します。 個人的な感想ですが、この二つを覚えるためにはひたすら繰り返し試してみるのが良いと思います。要するに慣れです。 出来上がったVIEWMODELは次の通りです。 ActressViewModel.swift ActressViewModel.swift import Foundation class ActressViewModel:ObservableObject{ @Published var actressViews:[ActressView] = [] init(){ fetchAndParse() } func fetchAndParse(){ let urlManager = UrlManager() guard let url = URL(string: urlManager.urlString)else{ return } //fetch URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data,error == nil else{ return } do{ //parse let actressModel:ActressModel = try JSONDecoder().decode(ActressModel.self, from: data) DispatchQueue.main.async { self.actressViews = actressModel.result.actress.map{ actress in return ActressView(actress: actress) } } }catch{ print(error.localizedDescription) } }.resume() } } 5 ContentView ContentViewは装飾を極力省いてシンプルにVIEWMODELからのVIEWをリストにして表示しているだけです。 ContentView.swift ContentView.swift import SwiftUI struct ContentView: View { @ObservedObject var viewModel = ActressViewModel() var body: some View { List{ ForEach(viewModel.actressViews){actressView in actressView } } } } 6 とりあえず出来上がり! ビルドすると できました! が、やっぱり名前だけだとつまらないので画像も表示できるように改造します。 7 画像を表示させる。 画像を表示させるライブラリはたくさんありますが、今回は簡易的なものを手作りします。 細かい説明は省きますが、画像を表示させるためにはinfo.plistを少しいじる必要があります。 なお、Xcode13からinfo.plistの場所が変わっちゃったみたいです。詳しくはこちらをご確認ください。 info.plistに App Transport Security Settings を追加し、 Allow Arbitrary Loads をYESにしてみてください。 画像をダウンロードして表示させる仕組みは基本的には今までのデータバインディングと非同期通信と同じです。 今回は細かい説明を省きます。 ImageView.swift ImageView.swift import SwiftUI struct ImageView:View{ @ObservedObject var imageLoader:ImageLoader init(imageUrl:String?){ imageLoader = ImageLoader(imageUrl: imageUrl) } var body: some View{ if let image = self.imageLoader.image{ Image(uiImage: image) }else{ Image(systemName: "person.crop.circle.badge.questionmark") } } } class ImageLoader:ObservableObject{ @Published var image:UIImage? = nil init(imageUrl:String?){ load(imageUrl: imageUrl) } func load(imageUrl:String?){ guard let imageUrl = imageUrl else{ print("string is nil") return } guard let url = URL(string: imageUrl)else{ print("bad url") return } DispatchQueue.global().async { do{ let data = try Data(contentsOf: url) DispatchQueue.main.async { self.image = UIImage(data: data) } }catch{ print("network error") } } } } 8 実行! ビルドしてみます。 できました! 一人だけ画像の情報がない女優がいたみたいですね。 そこもしっかり対応できています。 終わりに 今回は最低限の機能のものを作成しましたが、作り込めばいろんなことができます。色々試してみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIアプリにダークモード設定を追加する

SwiftUIアプリに、ユーザーがダークモードの設定を選ぶことができるプリファレンス設定を加えることができます:システムのスタイルに従うか、常にダークモードを使用するか、常にライトモードを使用するかを選択できます。 ユーザーデフォルトへのユーザーの環境設定の保存(@AppStorageを使用) ユーザーは、iOSシステムに追従するダークモード、常にダークモード、または常にライトモードを設定できます。 enum DarkModeSetting: Int { case followSystem = 0 case darkMode = 1 case lightMode = 2 } ユーザーのプリファレンス設定を確定したり取得したりするには、appearanceModeと呼ばれる変数を使用することができます: // import SwiftUI @AppStorage(wrappedValue: 0, "appearanceMode") var appearanceMode プリファレンス設定を追加 設定表示でプリファレンス設定を追加できるようになりました Picker("Appearance setting", selection: $appearanceMode) { Text("Follow system") .tag(0) Text("Dark mode") .tag(1) Text("Light mode") .tag(2) } ユーザー設定を適用し、好みのカラースキームを設定 App構造体で変数を読み取り、ダークモード設定を適用して下さい。 ダークモードの設定に対しては分岐条件が存在するので、カスタム関数を書くことで変更を適用できます。 アプリがシステムの設定に従うようにしたければ、優先すべきカラースキームpreferredColorSchemeをnilに設定します。 ユーザーが常にダークモードを使用することを選択すれば、.darkに設定します。 ユーザーが常にライトモードを希望すれば、.lightに設定します。 import SwiftUI @main struct SwiftUI_Toggle_DarkModeApp: App { @AppStorage(wrappedValue: 0, "appearanceMode") var appearanceMode var body: some Scene { WindowGroup { ContentView() .applyAppearenceSetting(DarkModeSetting(rawValue: self.appearanceMode) ?? .followSystem) } } } extension View { @ViewBuilder func applyAppearenceSetting(_ setting: DarkModeSetting) -> some View { switch setting { case .followSystem: self .preferredColorScheme(.none) case .darkMode: self .preferredColorScheme(.dark) case .lightMode: self .preferredColorScheme(.light) } } } 注意:優先配色が変更されると、アプリはデフォルトの画面に切り替わります。 完成したプロジェクト 完成したデモプロジェクトはこちらからダウンロードできます。 Twitter ツイッター @MszPro 私の公開されているQiita記事のリスト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhoneのカメラでリアルタイム物体検出 CoreMLモデルをつかえばかんたん

iPhoneのカメラで機械学習モデルによる物体検出をします スマホで物体検出できれば、色々つかえそう。でも、難しいそう 機械学習ってむずかしいのでは? 手軽に使えるCoreMLモデルで今すぐやってみれる アップルはiPhoneでかんたんに使えるCoreMLという形式を用意してくれています。 手順 アップルの公式モデルズーやCoreML-Modelsなどから物体検出モデルをダウンロード。 モデル実行リクエストを初期化。 guard let vnModel = try? VNCoreMLModel(for: yolov5().model) else { fatalError("Model initialization failed.") } self.request = VNCoreMLRequest(model: vnModel) カメラのフレームを食わせるだけ。 func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let handler = VNImageRequestHandler(cvPixelBuffer:buffer, orientation: .right, options: [:]) do { try handler.perform([self.coreMLRequest]) guard let results = self.coreMLRequest.results as? [VNRecognizedObjectObservation] else { return } for result in results { let label = observation.identifier let confidence = observation.confidence let boundingBox = observation.boundingBox print(label,confidence,boundingBox) } } catch let error { print(error) return nil } } フレーム毎に画像内で検出した物体のラベルと画像内座標を出力してくれます。 Optional("laptop") // 検出ラベル 0.6810466 // 信頼度 (0.002242632210254669, 0.002170730382204056, 0.21633444726467133, 0.09707524627447128) // 座標 視覚化はMLModelCameraといライブラリが便利です。 ? フリーランスエンジニアです。 お仕事のご相談こちらまで rockyshikoku@gmail.com Core MLやARKitを使ったアプリを作っています。 機械学習/AR関連の情報を発信しています。 Twitter Medium
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RealmSwift の deprecated warning に対応した

事象 先日、久しぶりに RealmSwift のバージョンアップをしたところ、下記のような警告が出るようになってしまいました。 ‘RealmOptional’ is deprecated: RealmOptional<T> has been deprecated, use RealmProperty<T?> instead. Use ‘RealmProperty’ instead 上記の警告を見て、RealmProperty の使い方を覚える必要があるかと思いましたが、実際はもっと良い解決方法が実装されていました。 解決方法 次のようなクラスがあったとした場合、 class Sample { let index = RealmOptional<Int>() // ここで警告が出る } 下記のように直します。 class Sample { @Persisted var index: Int? } また、主キーにしたい場合や インデックスをつけたい場合は下記のように宣言できます。 class Sample { @Persisted(primaryKey: true) var primaryKey: Int? @Persisted(indexed: true) var indexedProperty: Int? } 説明 リリースノートを見ると、RealmSwift のバージョン 10.10.0 から @Persisted プロパティラッパーが使えるようになり、これによって(Objective-C でサポートされていない型では)RealmProperty や RealmOptional は不要になった、というような記載があります。 また、@objc dynamic var で宣言していたプロパティも @Persisted var に置き換えられるようです。主キーやインデックスの設定も簡単になっています。 リリースノートより抜粋 Add a new property wrapper-based declaration syntax for properties on Realm Swift object classes. Rather than using @objc dynamic or the RealmProperty wrapper type, properties can now be declared with @Persisted var property: T, where T is any of the supported property types, including optional numbers and collections. This has a few benefits: - All property types are now declared in the same way. No more remembering that this type requires @objc dynamic var while this other type requires let, and the RealmProperty or RealmOptional helper is no longer needed for types not supported by Objective-C. - No more overriding class methods like primaryKey(), indexedProperties() or ignoredProperties(). The primary key and indexed flags are set directly in the property declaration with @Persisted(primaryKey: true) var _id: ObjectId or @Persisted(indexed: true) var indexedProperty: Int. If any @Persisted properties are present, all other properties are implicitly ignored. DeepL による日本語訳 Realm Swiftオブジェクトクラスのプロパティに、新しいプロパティラッパーベースの宣言構文を追加しました。objc dynamic や RealmProperty ラッパー型を使う代わりに、プロパティは @Persisted var property で宣言することができるようになりました。ここで T はサポートされている任意のプロパティタイプで、オプションの数値やコレクションを含みます。これにはいくつかの利点があります。 - すべてのプロパティタイプが同じ方法で宣言されるようになりました。この型には @objc dynamic var が必要で、他の型には let が必要だと覚える必要はありませんし、Objective-C でサポートされていない型には RealmProperty や RealmOptional ヘルパーは不要になりました。 - primaryKey()、indexedProperties()、ignoredProperties()などのクラスメソッドをオーバーライドする必要はもうないのです。主キーやインデックス付きのフラグは、プロパティ宣言の中で @Persisted(primaryKey: true) var _id と直接設定されます。ObjectId または @Persisted(indexed: true) var indexedProperty: Int. いずれかの @Persisted プロパティが存在する場合、他のすべてのプロパティは暗黙のうちに無視されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift, iOSでGraphQLクライアントを導入する

この記事について iOSアプリでGraphQLサーバにアクセスするためのクライアントを実装するための導入を書きます。サーバ機能ではなく、クライアント機能の説明です。 Swift iOS15 Xcode13 なお、サーバ側の実装については【Go言語】はじめてのGraphQLサーバ実装 | gqlgenも記述したのでご覧下さい。 手順 Apollo-iosパッケージのインストール GraphQLのライブラリではおなじみのapolloを利用します。 XcodeのFile -> Add Packages を開き、apollo-iosを検索してインストールします。 スキーマ定義のダウンロード apolloコマンドのインストール apolloコマンドがインストールされていなければインストールします。インストールにはnode.jsやJavaScriptでの開発でパッケージ管理でおなじみのnpmを使います。(npmコマンドのインストールについては割愛させていただきます。) $ npm install -g apollo スキーマのダウンロード GraphQLサーバに向けて下記のコマンドを実行します。ここではschema.jsonファイルにスキーマ定義が全て記述されます。 $ apollo schema:download --endpoint=http://localhost:8080/graphql schema.json schema.jsonをXcodeプロジェクト内に設置します。デフォルトではビルドターゲットのアプリの階層直下に保存します。つまり*.xcodeprojファイルがある階層の次のディレクトリです。(Assets.xcassetsディレクトリがある階層に保存) ビルド設定の追加 ここが難所です。Apollo公式ドキュメントの「Adding a code generation build step」と併せてご覧下さい。 ビルドターゲット・アプリケーションのBuildPhasesを開きます。 BuildPhasesで「+」ボタンをクリックし「New Run Script Phase」を選びます。「Run Script」が追加されます。 追加された「Run Script」を「Generate Apollo GraphQL API」に名称変更します。 タイトル部分をクリックすると変更できます。 あくまでも開発者が見て分かりやすい名前にします。 「Generate Apollo GraphQL API」を既存項目の「Compile Sources」の上に移動します。 Apollo公式ドキュメントの「Swift Package Manager Run Script」を画面のフォームに貼り付けます。 (必要に応じて)最後の行でschema.jsonのパスを編集します。 (必要に応じて)最後の行で生成されるSwiftファイルのパスを編集します。 ビルド 通常通りアプリケーションをビルドします。API.swiftが生成されます。ただ、プロジェクト内では管理されていないのでfinder等でソースコードを確認したら、Xcodeのプロジェクトに追加します。 エラー ここで下記のようなエラーに遭遇しました。こちらの投稿によるとapollo-iosの0.9.5バージョンではSwift5にきちんと対応できていないことが原因のようです。「Update Package」をして0.50.0にしたら解消しました。(2022年1月11日現在) Cannot convert parent type 'EnumeratedSequence<[Key]>' to expected type 'EnumeratedSequence<IndexingIterator<Array<Key>>>' クエリの実行 https://www.apollographql.com/docs/ios/initialization/#basic-client-creationにて基本的なクライントの実装方法が説明されています。シングルトン設計にしていますね。 GqlDriver.swift import Foundation import Apollo class GqlDriver { static let shared = ApiDriver() private (set) lazy var apollo = ApolloClient(url: URL(string: "http://localhost:8080/graphql")!) } 上記のGraphQL APIドライバクラスに対して下記のようにクエリを実行します。fetch関数でサーバに問い合わせています。引数のqueryには先ほどの自動生成で作成されたGraphQLクエリを指定します。 func func1() { GqlDriver.shared.apollo.fetch(query: UserQuery(id:"VXNlcjox")) { result in switch result { case .success(let r): // r に戻り値が含まれるので処理を書く。 case .failure(let e): print("\(e)") } } } 参考・関連文献 https://www.apollographql.com/docs/ios/installation/ https://www.apollographql.com/docs/ios/downloading-schema/ https://www.apollographql.com/docs/devtools/cli/ https://community.apollographql.com/t/error-building-swift-project-after-installing-apollo-ios-client/2308/2 https://www.apollographql.com/docs/ios/initialization/#basic-client-creation https://blog.spacemarket.com/code/graphql-on-swift-by-apollo-ios/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む