20191115のSwiftに関する記事は4件です。

Swift Package Managerで'No Such Module'した時の対処法

はじめに

iOSアプリ開発において、Xcode11からSwift Package Manager(以下、SPM)を公式に使えるようになりました。
導入を検討している人も多いかと思いますが、躓いた箇所があったので対処法の一例としてメモ程度に残しています。

なお、今回の対処法は「Getting 'no such module' error when importing a Swift Package Manager dependency - Stack Overflow」に記述がありました。

What

新しくライブラリを導入するにあたりSPMで管理していく方針にしました。

XcodeでFile → Swift Packages → Add Package Dependency... と順当にライブラリを取り入れるも

*.swiftファイルでimport Hogeと書いてもbuildできず、No such Moduleのエラーが出るばかりでした。

How

以下のスクリプトをTargets → Build Phases → New Run Script PhaseCompile Sourcesの手前に記述します

if [ -d "${SYMROOT}/Release${EFFECTIVE_PLATFORM_NAME}/" ] && [ "${SYMROOT}/Release${EFFECTIVE_PLATFORM_NAME}/" != "${SYMROOT}/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}/" ] 
then
  cp -f -R "${SYMROOT}/Release${EFFECTIVE_PLATFORM_NAME}/" "${SYMROOT}/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}/"
fi

今回はFix SPMという名称にしました。

Screen Shot.png

Why

SPMは暗黙的にXcodeプロジェクトのConfigurationを利用しており現状でデフォルトのReleaseDebugにしか対応していないようなので、ビルドしようとしているConfigurationと異なれば、Release用をコピーすることで解決しています。

Xcodeとしては既知の仕様らしいです。
参考: Custom build configuration names

また、上記サイトでは他の解決策も紹介されていたので是非参考にしてください
参考: Swift Package Hack - Álvaro Royo - Medium

現状はなんとかなりましたが、もっと最適な解決策があれば教えていただきたいです!
ありがとうございました!

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

R.generated.swift No such module 'プロジェット名' エラーの対処について

R.generated.swift:10:8: No such module 'プロジェット名'エラーの対処について

R.Swiftを導入して、リソース管理していましたが、Configrationをいじった後、上記エラーが出てしまう。

確かに、Configurationsにて既存のDebugとRelease以外にStagingと言うConfigurationsを追加してので、色々設定したので、その周りに問題があったか、調べたが、気になる所は見つからず...

Releaseモードでは上記エラーが無く無事にコンパイルされるが、DebugモードとStagingモードに切り替えてコンパイルすると、必ず、R.generated.swift:10:8: No such module 'プロジェット名' と言うコンパイルエラーが出てしまう。

一応、問題が発生するR.generated.swiftが生成する時、Releaseモードでは、import 'プロジェット名'が追加されないが、DebugモードとStagingモードでは自動に追加されるのだ。

ちらっとググって見ると、以下のように事例があるようですが、今度の問題と違うよう。。。
https://github.com/mac-cain13/R.swift/issues/326

暫くウロウロしてから、ついに、生成されるR.generated.swiftに目を通すようにした。

まず、'プロジェット名'をキーワードをして、R.generated.swift内部を検索して見たら、
複数のクラスの前に追加されたことがわかった。

R.generated.swift
import Foundation
import Rswift
import UIKit
import 'プロジェット名'

// ... ...

/// Segue identifier `toProduct`.
static let homeToProduct: Rswift.StoryboardSegueIdentifier<UIKit.UIStoryboardSegue, HomeViewController, 'プロジェット名'.ProductViewController> = Rswift.StoryboardSegueIdentifier(identifier: "toProduct")

// ... ...

なぜ、他のViewControllerクラスにはこのようクラス名前に、こんな'プロジェット名'がついてないか、直接、ProductViewControllerのストリボードを確認。すると

test.png

問題が起こすクラスにチェックがついてないまま、つまり、ビルド時洗濯されたConfigrationより生成されるモジュール名が異なる様に設定すると、ここにも影響がでることだった。

さっさと、Inherit Module From Targetにチェックを入れ、ビルドを掛け直してみる。

Release,Staging,Debug 全部生成されるR.generated.swiftに
import 'プロジェット名'追加されなくなり、クラス前にも'プロジェット名'.が消えて、無事ビルドに通った。

やった!

iOS、Androidアプリの制作なら、hq7781@gmail.comまで、
法人並みに、信頼且つ満足できる製品を納品いたします

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

SwiftUIで無限スクロールを実装する

はじめに

みなさんSwiftUI使っていますか?
自分も最近触り始めましたが、

  • ひたすらAutoLayoutのエラーと戯れたり
  • 都度ビルドして数秒放心状態になって時間を無駄にしたり

もなくなり開発効率が爆上がりしました :bomb:

さて今回はそんなSwiftUIを使って、↓のような無限スクロールを実装する方法を紹介します。

Vertical Horizontal

ただし先に結論を言っておくと、SwiftUI側で機能的に足りないところがあり、思ったように実装できなかった箇所も多いです。そこについても詳しく説明していきます。

ライブラリとしても公開したのでとりあえず動かしてみたいという方はこちらをご覧ください。
最近デザインも勉強してるので、テンション上げるためにロゴも作ってみました :tada:

image.png

https://github.com/kazuooooo/SwiftUIInfinityScroll

対象読者

  • SwiftUIで無限スクロールを実装したい方
  • ウォンバットが好きな方

実装の仕組み

無限に要素を生成する仕組み

無限スクロールを実装する方法…一度自分の頭でも考えてみて欲しいですが、どんな方法が思いつきますか?
自分も3つくらいアイデアを思いついたのですが、最終的に採用したのが以下の図のようなものです。

image.png

  1. N個の要素を用意しておき画面に配置します。ユーザーは画面をスクロールしていきます
  2. N - 1番目のViewがonAppearしたタイミングでN+1個目を生成します。
  3. 要素が1つ増えるので今見ているViewはN - 2番目になります。
  4. 2に戻る。無限に続く...

実装としては@ObservedObjectを使って、ViewModelを作成して状態管理を行なっています。
以下のようなものになっています。(※重要な部分だけ抜粋しています。)

viewmodel
open class InfinityScrollViewData: ObservableObject {
    @Published var items: [G.Item]

    func onAppear(page: Int){
        // 初期化
        if(isOnInitialize(appearedPage: page)) {
            items.append(generator.generateItem(page: 1))
            items.append(generator.generateItem(page: 2))
        }

        // N - 1個目の要素が表示されたら要素を追加する
        if(needToAppendItem(appearedPage: page)) {
            items.append(generator.generateItem(page: lastPage + 1))
        }
    }

    private func isOnInitialize(appearedPage page: Int) -> Bool { page == 0 && items.count == 1 }
    private func needToAppendItem(appearedPage page: Int) -> Bool {
        page == lastPage - 1
    }
}
view
public struct InfinityScrollView: View {
    // 状態管理
    @ObservedObject var scrollViewData: InfinityScrollViewData<G>

    public init(generator: G, direction: InfinityScrollDirection) {
        self.scrollViewData = InfinityScrollViewData<G>(generator: generator)
        self.scrollDirection = direction
    }

    public var body: some View {
        GeometryReader { geometry in
            VStack(alignment: .center) {
                List(self.scrollViewData.items) { item in
                    item
                    .onAppear(){
                        // Viewが画面上に表示されたらonAppearをコール
                        self.scrollViewData.onAppear(page: item.page)
                    }
                }
            }
       }
    }
}

横方向のスクロール

ListViewには横方向がないので、横方向のスクロールに対応するために、なんとLIstを90度に回転させてさらに中の要素を
-90度回転させるという暴挙に出ています笑

...
    var viewRotation: Double {
        switch(scrollDirection){
        case(.horizontal):
            return 90
        case(.vertical):
            return 0
        }
    }
...

    public var body: some View {
        GeometryReader { geometry in
            VStack(alignment: .center) {
                List(self.scrollViewData.items) { item in
                    item
                        // 回転させた分中身を逆方向に回転
                        .rotationEffect(.degrees(self.viewRotation))
                }
                // Listを横に回転
                .rotationEffect(.degrees(-self.viewRotation))
            }
       }
    }

ここで

「いやそもそもお前Horizontal View使えばええやんwww知らんのかいなwww てかそもそもなんでScrollView使わんの? 」 :laughing:

と思った関西の方。
はい、自分もそう思いました。
ここからはScrollViewで実装しようとしておきた問題点について書いていきます。

ScrollViewの問題点

ScrollViewを使えば以下のようなコードで横方向のスクロールは比較的容易に実装することができます。

ScrollView (.horizontal, showsIndicators: false) {
     HStack {
         //contents
     }
}.frame(height: 100)

しかしながらこれを無限スクロールにしようとすると色々問題が起きてきます。

OnAppearが画面上になくても発火する

まず、ScrollViewに要素を追加すると、画面上に表示されている / いないに関わらずOnAppearが発火します。
なのでList Viewで使った上記の無限に要素を生成する仕組みを使うことができません。

スクロール位置を取れない

じゃあ別にScroll位置を取得して、その位置に応じて要素生成すれば良いとなりますが、それも自分が探した限りは見当たりませんでした。
なので、もしやるとするならGeometryReaderをつけた見えない要素をScrollViewの端とかに配置して、Scroll位置を取得してそこから現在の表示領域を計算するという方法がありますが、なんか強引な気がしますしロジックが複雑になりそうな気がして、一旦見送りました。

ListView & ScrollView共通の問題

手前のViewを消すと位置がずれる

パフォーマンスを考えて、見えなくなったViewを消す処理を入れてみたところ、手前のViewを消すと表示位置がずれてしまいました。そのため、表示されなくなったViewも特に何もせずにSwiftUIに処理を任せています。
ある程度最適化されているのか、現状そこまでパフォーマンスの問題は起きていませんが結構気持ち悪い状態です。

描画位置をコントロールできない

上記の位置がずれる問題をずれた分描画位置を逆方向にずらせば、動作させられることも考えられますが、その機能も現状のSwiftUIにはありません。
また、

  • フリックしたときに1ページずつぴったりのところに表示する機能
  • 逆方向へのスクロール

も入れたかったのですが、同様の機能がなく今回は実装できていません。
UIKitにはcontentOffsetがあるので、この機能がSwiftUIにも入ることを期待しています。

まとめ

いかがだったでしょうか?
ちょっと今回作成したライブラリをそのままプロダクトに使えるほどではないので、それを期待した方はごめんなさい ?‍♂️
自分のプロダクトでも使いたいのでSwiftUIのバージョンアップを待ちつつももう少しクオリティ上げていくつもりです。
(Contributeも歓迎です :frog:)

感想としては
「SwiftUiまだまだ痒いところに手が届かないなー泣 :cry:
という感じです。
とはいえ
「開発しやすくて好きだよSwiftUI :heart:
という気持ちもかなりあるのでこれからも使っていく所存です。

その他 〜SwiftUIおすすめリンク〜

日本語

SwiftUI実践入門
とても薄い本にしっかりとSwiftUIの重要な部分がまとまっています。
日本語 & わかりやすいのでシュッと入門するなら英語のチュートリアルやるよりいいかもしれません。

英語

Swift公式チュートリアル
言わずもがな。Appleがかなり力を入れて作ったと思われる。むしろこっちのWebの実装がどうなってるのか気になる。

HackingWithSwift
これはどうやるんだろう?というのを調べるときに短いサンプルコードがあるので、とても良い。
またState周りが若干SwiftUIの鬼門だがこの動画が非常にわかりやすく説明されている。
(※若干バージョンが古い。ただ概念は同じなので参考になる)

SwiftUI使ったことない方は是非試してみてください :deciduous_tree:

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

swift⇄objective-cのファイル間での連携

Swift⇄objective-cのファイル間でのやりとりの方法を稀に忘れてしまうので備忘録としてここに記す。

objective-cからSwiftを呼び出す方法

Swiftのクラス名に@objcを書きNSobjectを継承させる

@objc class test: NSObject {
    func test1() {
        print("テスト")
    }
}

呼び出したいメソッドに@objcを書く

@objc class test: NSObject {
    @objc func test1() {
        print("テスト")
    }
}

ちなみにクラスの頭に@objcMembersを書くとクラス全体に対して一括して@objcとして設定することが可能

@objcMembers
class test: NSObject {
    func test1() {
        print("テスト")
    }
}

Swiftからobjective-cを呼び出す方法

<プロジェクト名>-Bridging-Header.h

<プロジェクト名>-Bridging-Header.hに呼び出すobjective-cのヘッダーファイルを記載してあげる

#import "ObjcTest.h"

objective-cの.m実装ファイル側でimportする

#import "名前-swift"

objective-cのヘッダーファイルに参照するメソッドを書く

- (int)test;

締め

必ずビルトかけてから参照しよう!
漏れがあるかもしれないのでほかあれば教えてください。

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