カスタムピッカー初期化メソッドでの「タイプの戻り式を変換できません」エラー

概要

SwiftUI Picker で拡張機能をビルドしようとしていますが、コンパイルしようとするとコンテンツ クロージャ内で次のエラーが発生します。

コードは次のとおりです。

extension Picker where SelectionValue: Identifiable, Label == Text {
    
    init<Data>(_ title: String, selection: Binding<SelectionValue>, items: Data, @ViewBuilder itemContent: @escaping (Data.Element) -> some View) where Data: RandomAccessCollection, Data.Element == SelectionValue {
        self.init(title, selection: selection, content: {
            ForEach(items) { item in
                itemContent(item).tag(item)
            }
        })
    }
}

ForEach は View に準拠する必要があるため、Content type の要件を満たす必要があります。

解決策

コンテンツを何にも制約していないため、init はコンテンツ タイプ パラメーターが任意のビューであるピッカーを作成できることが期待されています。ただし、init で作成したピッカーのコンテンツ タイプは ForEach<…> です。

これは、コンテンツに制約を追加する必要があることを意味します。

// note that you should also parameterise this with ItemContent
// you can't use (Data.Element) -> some View anymore, because you need the return type
// of that to constrain Content
init<Data, ItemContent>(
    _ title: String,
    selection: Binding<SelectionValue>,
    items: Data,
    @ViewBuilder itemContent: @escaping (Data.Element) -> ItemContent
) where
    Data: RandomAccessCollection,
    Data.Element == SelectionValue,
    Content == ??? // <=== here

では、ForEach(items) { … } の型は何でしょうか? .tag 修飾子が返す型がわからないため、実際にはわかりません。

これを回避する簡単な方法の 1 つは、この ForEach をラップする独自のビューを作成することです。

struct PickerContentHelper<Data, Content>: View
    where Data: RandomAccessCollection, 
        Data.Element: Hashable,
        Data.Element: Identifiable,
        Content: View
{
    
    let items: Data
    let itemContent: (Data.Element) -> Content
    
    init(_ items: Data, @ViewBuilder itemContent: @escaping (Data.Element) -> Content) {
        self.items = items
        self.itemContent = itemContent
    }
    
    var body: some View {
        ForEach(items) { item in
            itemContent(item).tag(item)
        }
    }
}

次に、Content を PickerContentHelper<Data, ItemsContent> に制限できます。

extension Picker where SelectionValue: Identifiable, Label == Text {
    
    init<Data, ItemContent>(
        _ title: String,
        selection: Binding<SelectionValue>,
        items: Data,
        @ViewBuilder itemContent: @escaping (Data.Element) -> ItemContent
    ) where
        Data: RandomAccessCollection,
        Data.Element == SelectionValue,
        Content == PickerContentHelper<Data, ItemContent>
    {
        self.init(title, selection: selection) {
            PickerContentHelper(items, itemContent: itemContent)
        }
    }
}

これは、他の多くの SwiftUI ビューで見られる一般的なパターンです。これらの「ヘルパー ビュー」の多くは、「サポートするタイプ」セクションにあります。たとえば、ShareLink には DefaultShareLinkLabel があり、init(item:subject:message:) などの初期化子は、Label タイプ パラメーターをこのタイプに制約します。