I am making an app with various screens, and I want to collect all the screens that all conform to View
in an array of views. On the app's main screen, I want to show a list of all the screen titles with a NavigationLink
to all of them.
The problem I am having is that if I try to create a custom view struct and have a property that initialises to a given screen and returns it. I keep running into issues and the compiler forces me to change the variable to accept any View
rather than just View
or the erased type some View
.
struct Screen: View, Identifiable {
var id: String {
return title
}
let title: String
let destination: any View
var body: some View {
NavigationLink(destination: destination) { // Type 'any View' cannot conform to 'View'
Text(title)
}
}
}
This works:
struct Screen<Content: View>: View, Identifiable {
var id: String {
return title
}
let title: String
let destination: Content
var body: some View {
NavigationLink(destination: destination) {
Text(title)
}
}
}
But the issue with this approach is that I cannot put different screens into an Array which can be fixed as follows:
struct AllScreens {
var screens: [any View] = []
init(){
let testScreen = Screen(title: "Charties", destination: SwiftChartsScreen())
let testScreen2 = Screen(title: "Test", destination: NavStackScreen())
screens = [testScreen, testScreen2]
}
}
But when I try to access the screens in a List it cannot infer what view it is without typecasting. The end result I am trying to achieve is being able to pass in an array of screens and get their title displayed in a list like below.
At the moment I can only achieve this by hardcoding the lists elements, which works.
import SwiftUI
struct MainScreen: View {
let screens = AppScreens.allCases
let allScreens = AllScreens()
var body: some View {
NavigationStack() {
List() {
AppScreens.chartsScreen
AppScreens.navStackScreen
}
.navigationTitle("WWDC 22")
}
}
}
CodePudding user response:
SwiftUI is data-driven reactive framework and Swift is strict typed language, so instead of trying to put different View types (due to generics) into one array (requires same type), we can make data responsible for providing corresponding view (that now with help of ViewBuilder is very easy).
So here is an approach. Tested with Xcode 13 / iOS 15 (NavigationView or NavigationStack - it is not important)
enum AllScreens: CaseIterable, Identifiable { // << type !!
case charts, navStack // << known variants
var id: Self { self }
@ViewBuilder var view: some View { // corresponding view !!
switch self {
case .charts:
Screen(title: "Charties", destination: SwiftChartsScreen())
case .navStack:
Screen(title: "Test", destination: NavStackScreen())
}
}
}
usage is obvious:
struct MainScreen: View {
var body: some View {
NavigationStack { // or `NavigationView` for backward compatibility
List(AllScreens.allCases) {
$0.view // << data knows its presenter
}
.navigationTitle("WWDC 22")
}
}
}