0

Context: I'm following the tutorial on https://developer.apple.com/tutorials/swiftui/ to get an introduction to Swift and iOS.

In learning about closure notation in Swift, I wanted to write the more expanded version of a closure I came across.

ForEach(filteredLandmarks) {
  landmark in
      NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
        LandmarkRow(landmark: landmark)
    }
}

The first code snippet works great.

From what I understand about trailing closures, is that the type of the function needed as the last argument to the ForEach struct is implies the type of the function defined by the closure, hence we don't need to explicitly say the type of the function defined by the closure and refer only to it's one and only argument landmark implicitly. But what is the type if I wanted to explicitly define it?

The following is my attempt, which produces the error in Xcode

value of protocol type 'View' cannot conform to 'View'; only struct/enum/class types can conform to protocols

ForEach(filteredLandmarks) {
  (landmark: Landmark) -> View in
      NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
        LandmarkRow(landmark: landmark)
    }
}

I know landmark has a type of Landmark, because removing the -> View works perfectly. So really, all I need to know is what is the return type?

2 Answers2

0

Figured it out soon after posting, funny how that happens.

The question has the answer, because the closure is returning something, so we just need to know what type is being returned.

The closure in question is returning a NavigationLink

NavigationLink<Label, Destination> where Label: View, Destination: View

from the docs.

The Destination parameter of NavigationLink is explicitly identified as a LandmarkDetail object. The Label parameter is defined by the trailing closure. Here's the answer.

ForEach(filteredLandmarks) {
  (landmark: Landmark) -> NavigationLink<LandmarkRow, LandmarkDetail> in
      NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
        LandmarkRow(landmark: landmark)
    }
}
0

You are calling this initialiser:

init(_ data: Data, content: @escaping (Data.Element) -> Content)

(Available when Data conforms to RandomAccessCollection, ID is Data.Element.ID, Content conforms to View, and Data.Element conforms to Identifiable.)

As you can see, the closure is supposed to return Content. What is Content?

ForEach is actually a generic type, and Data and Content are actually the generic type parameters of ForEach:

struct ForEach<Data, ID, Content> 
    where Data : RandomAccessCollection, ID : Hashable

This means that you get to decide what Content is.

Using -> View doesn't work, because for that initialiser to be available, Content must conform to View. View does not conform to View, so you can't do that.

In your code, you are returning a NavigationLink, and that is indeed the type of Content for ForEach. You should do:

(landmark: Landmark) -> NavigationLink in

However, the catch is, NavigationLink is also generic! You must specify its generic parameters!

The type of the destination parameter is the second generic parameter, and the view you return in the trailing closure is its first generic parameter. Assuming LandmarkDetail and LandmarkRow are not generic, you'd need to do:

(landmark: Landmark) -> NavigationLink<LandmarkRow, LandmarkDetail> in

Note that most of the time when writing SwiftUI, such analysis of what type the closure returns is not possible. Suppose you did:

ForEach(filteredLandmarks) {
  landmark in
      NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
        LandmarkRow(landmark: landmark).padding()
    }
}

instead, then we would not be able to know the exact type of the first generic parameter of NavigationLink, as padding returns an opaque type of some View. Note that you can do this in your closure return type too:

(landmark: Landmark) -> some View in
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Awesome! I was having a hard time finding the init documentation for anything I was searching for as it appears after examples which I wasn't expecting. I wouldn't say "such analysis of what type the closure returns is not possible". The example of an opaque type being returned is very helpful and informative, but that is exactly what type is being returned, so such analysis is definitely possible, just with an opaque type, instead of a regular type, as you stated. Maybe say such analysis of what type the closure returns is more ambiguous. – Landon Henrie Apr 26 '21 at 17:48