5

I successfully implemented PageView within SwiftUI via thread:
How to implement PageView in SwiftUI?

Passing in multiple Views via an Array works like a charm, as long as all views are of the same struct.
PageView([TestView(), TestView()]).
However, I'd like to pass in different views.
PageView([TestView(), AnotherView(), DifferentView()]).

All views are of SwiftUI type:
struct NAME : View { code }

When I try to add different structs to an array I get the following error message:

var pageViewViewArray = [TestView(), AnotherView(), DifferentView()]

Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional.
Insert' as [Any]

By casting it to:

var pageViewViewArray = [TestView(), AnotherView(), DifferentView()] as! [Any]
PageView(pageViewViewArray)

PageView will say:

Protocol type 'Any' cannot conform to 'View' because only concrete types can conform to protocols

I'll greatly appreciate any ideas.

Peanutsmasher
  • 220
  • 3
  • 13

2 Answers2

8

Try using type erasure by casting every view to AnyView:

var pageViewViewArray: [AnyView] = [AnyView(TestView()), AnyView(AnotherView()), AnyView(DifferentView())]

Documentation here, and an example of using it here.

RPatel99
  • 7,448
  • 2
  • 37
  • 45
  • 1
    This resolved the issue perfectly, even including documentation and an example. Thank you! – Peanutsmasher Oct 21 '19 at 19:41
  • 1
    Just remember that that AnyView is expensive, so only use it when absolutely necessary (which, in this case, it is). – Peter Schorn Aug 20 '20 at 04:01
  • @Peanutsmasher this solution will significantly reduce SwiftUI’s ability to efficiently update your views. You can (**and should**) avoid this. Please, take a look at my answer below. – spafrou Mar 10 '21 at 15:41
1

There is a more efficient way to do it, without type erasure. You should create a common view in which you inject an enum value based on which you then return a desired view. Take a look at an example below:

/// The first type of a view that accepts and shows a number
struct ViewA: View {

    let number: Int

    var body: some View {
        Text("\(number)")
    }
}

/// The second type of a view that accepts and shows a string
struct ViewB: View {

    let string: String

    var body: some View {
        Text(string)
    }
}

/// An enum type used for dependency injection
enum ViewType {

    case viewA(number: Int)
    case viewB(string: String)
}

/// A common view which internally handles different kind of views based on injected enum value
struct CommonView: View {

    let viewType: ViewType

    var body: some View {
        switch viewType {
        case .viewA(let number):
            ViewA(number: number)

        case .viewB(let string):
            ViewB(string: string)
        }
    }
}

// Views used for page view controller, that are now of same type:
var pageViewViewArray = [
    CommonView(viewType: .viewA(number: 1)),
    CommonView(viewType: .viewB(string: "Hello, World!"))
]
spafrou
  • 548
  • 1
  • 6
  • 19
  • I don't believe SwiftUI allowed switch or if statements back when I created my answer, but this seems to work! Do you mind telling me why this method allows two different View types to be returned (ViewA and ViewB), thus bypassing the "Function declares an opaque return type, but the return statements in its body do not have matching underlying types" that normally appears if we try to conditionally return two different View types in the body? – RPatel99 Mar 12 '21 at 10:56
  • Switch statements inside function builders are part of Swift 5.3. It works because the body actually doesn't return two different types. It returns the following type: `_ConditionalContent`. You can check this with the following code: `let view = CommonView(viewType: .viewA(number: 1)); print(type(of: view.body))` – spafrou Mar 12 '21 at 11:19
  • Ah, got it. How come using an `if` statement to return different `View`s doesn't work then? I thought using if statments also returned a `ConditionalContent`? – RPatel99 Mar 12 '21 at 11:27
  • There is a difference between a normal function with an if statement inside it and a @ViewBuilder function. The first one returns two different types, thus the code won't compile. The second one returns a `_CondtionalContent`. – spafrou Mar 15 '21 at 14:28