UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Problems passing a custom view to NavigationLink destination

Forums > SwiftUI

I have a project with multiple views. Each view is in its own file. When I create my navigation link in ContentView() I can't get the parameters correct for calling the next view. I have been spending 4 days trying to figure this out by watching videos, reading and researching to no avail. I created each view and it works when display its preview, but when I moved the regions list to the ContentView() I can't get it to work: Thanks for any help. Here is my code:

ContentView file

struct Landmark:Identifiable{
     var id: Int
    var title:String
    var description:String
    var leftImage:String
    var rightImage:String
    var lat:Double
    var lon:Double
}

struct Regions:Identifiable{
    var id: Int
    var title: String
    var description: String
    var mainImage: String
   }

struct ContentView: View {
      var body: some View {
        NavigationView {

            List {
                ForEach(regions) { user in
                    //NavigationLink(destination: Text(user.title)) {
                    NavigationLink(destination: RegionView(regionList: Landmark)) {
                        ZStack {
                            VStack(alignment: .leading){
                                Image(user.mainImage)
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .cornerRadius(25)
                                HStack {
                                    Text(user.title)
                                        .font(.system(size: 15))

                                }
                            }
                        }

                    }  //.background(Color.gray)
                } .navigationBarTitle(Text("SCOTLAND REGIONS"))
            }
        }

    }

}

My landmarkData is contained in a seperate file:

var landmarkData: [Landmark] = [
    .init(id: 0, title: "Edinburgh Castle", description: "Edinburgh Castle is a historic fortress which dominates the skyline of Edinburgh, the capital city of Scotland, from its position on the Castle Rock. Archaeologists have established human occupation of the rock since at least the Iron Age, although the nature of the early settlement is unclear.", leftImage: "Edinburgh1", rightImage: "test", lat: 55.94851, lon: -3.20015)
        ]

my regions variable is in a seperate file:

var regions: [Regions] = [

    .init(id: 0, title: "Aberdeen & Aberdeenshire", description: "Put description here", mainImage:"edinburgh3"),

    .init(id: 1, title: "Argyll & The Isles", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 2, title: "Ayrshire & Arran", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 3, title: "Dumfries & Galloway", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 4, title: "Dundee & Angus", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 5, title: "Edinburgh & The Lothians", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 6, title: "Greater Glasgow & The Clyde Valley", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 7, title: "Loch Lomond, The Trossachs, Stirling & Forth Valley", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 8, title: "Orkney", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 9, title: "Outer Hebrides", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 10, title: "Perthshire", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 11, title: "The Highlands", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 12, title: "The Kingdom of Fife", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 13, title: "Shetland", description: "Put description here", mainImage:"edinburgh3"),
    .init(id: 14, title: "Scottish Borders", description: "Put description here", mainImage:"edinburgh3")

]

My RegionView that is to be called after a region is selected is in a seperate file:

struct RegionView: View {
  //   var itemList: Landmark
    let regionList: Landmark

    @State var selection: Int? = nil

    var body: some View {

      //  NavigationView {
            VStack {
                VStack {
                    ZStack (alignment: .bottom) {
                        //Image
                        Image("Edinburgh1")
                            .resizable()
                            //.aspectRatio(contentMode: .fit)
                            .frame(width: 250.0, height: 250.0, alignment: .center)
                            .clipShape(Circle())
                            .scaledToFit()
                            .shadow(radius: 10)
                            .overlay(Circle().stroke(Color.black, lineWidth: 5))

                        ZStack {
                            Text("Edinburgh")
                                .font(.callout)
                                .padding(6)
                                .foregroundColor(.white)
                        }.background(Color.black)
                        .opacity(0.8)
                        .cornerRadius(10.0)
                        .padding(6)
                            //Spacer()

                    }
                }//.edgesIgnoringSafeArea(.top)

                //Spacer()
                VStack{

                    Text("Edinburgh is Scotland's compact, hilly capital. It has a medieval Old Town and elegant Georgian New Town with gardens and neoclassical buildings. Looming over the city is Edinburgh Castle, home to Scotland’s crown jewels and the Stone of Destiny, used in the coronation of Scottish rulers. Arthur’s Seat is an imposing peak in Holyrood Park with sweeping views, and Calton Hill is topped with monuments and memorials.")
                        .font(.body)
                        .lineLimit(15)
                        .lineSpacing(5.0)
                        .padding()
                    // .frame(maxHeight: 310)
                }
                Spacer()
               //     .frame(height: 250)

                  HStack(spacing: 22.0) {

                    VStack {

                        NavigationLink(destination: RegionalList(getDetail: regionList)
                                        .edgesIgnoringSafeArea(.all), tag: 1, selection: $selection) {

                     //   NavigationLink(destination: MapView(lat: 30.191, lon: -90.123, title: "Here")
                     //                   .edgesIgnoringSafeArea(.all), tag: 1, selection: $selection) {

                            Button(action: {
                                self.selection = 1
                            }) {
                                Image("poi")
                            }
                                        }
                        Text("POI")
                            .font(.footnote)

                    }
                    VStack {

                        Button(action: {

                        }) {
                            Image("dining")
                        }

                        Text("Dining")                }
                        .font(.footnote)
                    VStack {

                        Button(action: {

                        }) {
                            Image("museum")
                        }

                        Text("Museum")
                            .font(.footnote)

                    }
                    VStack {

                        Button(action: {

                        }) {
                            Image("church")
                        }

                        Text("Churches")
                            .font(.footnote)

                    }
                    VStack {

                        Button(action: {

                        }) {
                            Image("castle")
                        }

                        Text(" Castles")
                            .font(.footnote)

                    }

                }

            }//.edgesIgnoringSafeArea(.all
           // .navigationBarHidden(true)

        }

   // }

}

3      

The error I am getting for line:

NavigationLink(destination: RegionView(regionList: Landmark)) {

is

Cannot convert value of type 'Landmark.Type' to expected argument type 'Landmark'

3      

So the idea seems to be that you have a list of regions and each region has a list of landmarks within it. In your ContentView you want to display the list of regions and let the user select one, at which point you switch to the RegionView and display the landmarks of the selected region along with some other info. Right?

You should set up your Region struct like so:

struct Region:Identifiable{
    var id: Int
    var title: String
    var description: String
    var mainImage: String
    var landmarks: [Landmark]
}

So each Region has its own set of Landmark items. You would also need to adjust your data source accordingly.

Then your ContentView like so:

struct ContentView: View {
    var body: some View {
        NavigationView {

            List {
                ForEach(regions) { region in
                    NavigationLink(destination: RegionView(region: region)) {
                        ZStack {
                            VStack(alignment: .leading){
                                Image(region.mainImage)
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .cornerRadius(25)
                                HStack {
                                    Text(region.title)
                                        .font(.system(size: 15))

                                }
                            }
                        }

                    }  //.background(Color.gray)
                } .navigationBarTitle(Text("SCOTLAND REGIONS"))
            }
        }

    }

}

And your RegionView like so:

struct RegionView: View {
    //   var itemList: Landmark
    let region: Region

    var body: some View {
        //...all your body stuff here
        //   presumably with a List looping
        //   through region.landmarks
        //   along with whatever else you
        //   want to display about the region
    }
}

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

One thing that can help is how you name things. So, for instance, a RegionView clearly takes in a Region item to display. And within that RegionView, you would have a LandmarkListView or something that takes an array of Landmark items. That does tend to lead to funny-sounding stuff like RegionView(region: region) but it sure helps with clarity and figuring out what goes where.

3      

@roosterboy, Thanks for your reply. I was finally able to get my ContentView() working after I changed this line:

NavigationLink(destination: RegionView(regionList: Landmark)) {

to

NavigationLink(destination: RegionView(regionList: edinburghCastles[0])) {

I do plan on changing my Region struct like you said, but hand't gotten their yet. I wanted to make sure that what I had so far was working. In fact, It will be a bit more complicated than that. Each Region will have a choice of 5 different lists of landmarks, for example, POI, Dining, Museums, Castles, Churches.

After each list is displayed, then you can get to choose a specific item and it will display the detail info about that item, and a choice to view the location on a map. Most of this is working. Still have to curate all of my images and info about each location.

Yes you are right, naming can get a little confusing. Again, thanks for the help.

3      

I managed to get my Region struct changed with a variable poi: [Landmark] inside of it.

When I call my RegionList View to display a list of POI's for that region, I am having a hard time figuring how to pass the info on to the next view:

struct RegionalList: View {

    let region: Region

    var body: some View {

              ForEach(region) { region in

                    NavigationLink(destination: DetailView(region: region.poi)) {

I get the following errors:

ForEach(region) { region in

Get's this error: Cannot convert value of type 'Region' to expected argument type 'Range<Int>'

And

NavigationLink(destination: DetailView(region: region.poi)) {

Get's these errors: Unnamed argument #2 must precede argument 'destination' alue of type 'Int' has no member 'poi'

This is based on using the struct:

struct Region:Identifiable{
    var id: Int
    var title: String
    var description: String
    var mainImage: String
    var poi: [Landmark]

}

3      

Right, because RegionalList only has a single Region, not an array. So you can't loop through a single item.

Instead, you would need to loop through the poi array:

ForEach(region.poi) { poi in
    NavigationLink(destination: DetailView(region: poi)) {
        //...content
    }
}

(Although the argument for DetailView should probably be labeled more appropriately, like points or details or something. Because a DetailView doesn't take a Region but rather a list of points of interest within a Region. This is what I meant earlier about naming.)

3      

You are correct in that the naming is an issue. Working on fixing that. I can get a list of my poi array, but I have since changed up mu Region struct like this:

struct Region:Identifiable{
    var id: Int
    var title: String
    var description: String
    var mainImage: String
    var poi: [Landmark]
    var dining: [Landmark]
    var museum: [Landmark]
    var church: [Landmark]
    var castle: [Landmark]
 }

At the bottom of my RegionView() I have 5 buttons with actions that I won't to send the approriate Landmark list to my RegionalList() view. My action button is like this:

VStack {
                        NavigationLink(destination: RegionalList(region: region)
                                        .edgesIgnoringSafeArea(.all), tag: 1, selection: $selection) {
                            Button(action: {
                                self.selection = 1
                            }) {
                                Image("dining")
                            }
                                        }
                        Text("Dining")
                        .font(.footnote)

                    }

Do I need to change the NavigationLink here, or the ForEach in the RegionalList() ? This is all I have left to get the app fully functional before I start adding all of my data.

3      

What I would do is in your RegionView, add an @State property to track which Landmark listing is passed to the RegionalView. Then your buttons would assign region.poi, region.dining, region.museum, etc as needed. Something like this:


struct RegionView: View {
    let region: Region

    @State private var listing: [Landmark] = []
    @State private var goToListing = false

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: RegionalList(listing: listing), isActive: $goToListing) {
                    EmptyView()
                }

                Button("POI") {
                    listing = region.poi
                    goToListing = true
                }
                Button("Museums") {
                    listing = region.museums
                    goToListing = true
                }
                //...and so on...
            }
        }
    }
}

3      

In my RegionView() I originally had a NavigationLink() for each button seperately. I changed it to what you showd, and it makes that part a lot simpler with less code.

However, I can't get past the error: Cannot convert value of type '[Landmark]' to expected argument type 'Landmark' for

NavigationLink(destination: RegionalList(listing: listing), isActive: $goToListing) {
                    EmptyView()
                }

In my RegionalList() View I put a variable:

var selection: Landmark

I tried using a @State, but this didn't help with passing the info to the next view.

3      

Since a RegionalList presumably displays, well, a list, you need a list there. That's an array.

Landmark is one item. [Landmark] is an array of items.

3      

Ok. I really appreciate your help with this. Everything is working perfectly now. The last part [Landmark] I had tried several times and couldn't get it to work. I was even cleaning my build, and it didn't work. Then XCode crashed. When I restarted, [Landmark] worked. Not sure what was going on there. But it works!!!

Again, thanks for your help. I owe you a beer!

3      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.