Hi, I am inexperienced hobbyist programmer in the Xcode environment and the swift language.
I have a very basic question: I read the documentation about one of the NavigationSplitView initializers:
init(sidebar: () -> Sidebar, detail: () -> Detail)
I created two basic views the SidebarView and the DetailsView.
The following code, use the views above as parameters but gives errors, and I cannot understand why.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationSplitView(sidebar: SidebarView(), detail: DetailsView())
}
}
#Preview {
ContentView()
}
I am interested to understand how the swift language works, and how to implement the documentation of swiftUI in real code.
Is there any way to use the views as parameters and not in brackets?
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationSplitView {
SidebarView()
} detail: {
DetailsView()
}
}
}
#Preview {
ContentView()
}
Hi. I'm glad you asked this because I'm constantly tripping over SwiftUI syntax myself. There are a bunch of ways to express the contents of your NavigationSplitView.
Note that SwiftUI is not Swift. It looks like Swift, it is written in Swift, but it is a domain specific language for creating descriptions of user interfaces. It extends the Swift language.
If you look at the documentation for NavigationSplitView
in Xcode's Documentation viewer, you'll find
struct NavigationSplitView<Sidebar, Content, Detail> where Sidebar : View, Content : View, Detail: View
and if you click on View, you'll find that it is a protocol.
The initializer you are using is the one which creates a two-column view, with no control over the visibility of the columns
public init(@ViewBuilder sidebar: () -> Sidebar, @ViewBuilder detail: () -> Detail) where Content == EmptyView
The parameters (labelled sidebar
and detail
here) are closures - functions returning structs conforming to the View protocol.
my SidebarView
and DetailView
are Views
defined like this
struct SidebarView: View {
var body: some View {
Text("sidebar")
}
}
struct DetailView: View {
var body: some View {
Text("detail")
}
}
And here are functions which return these structs
func DetailViewFunc() -> DetailView {
return DetailView()
}
func SidebarViewFunc() -> SidebarView {
return SidebarView()
}
we can use these functions as parameter values:
struct ContentView: View {
var body: some View {
NavigationSplitView ( sidebar: SidebarViewFunc , detail: DetailViewFunc)
}
}
that's a bit verbose, but it does look like a regular function call with a parameter list.
More succinctly, you can just write the functions directly in the parameter list, without names. You can omit the return keyword because of the use of @ViewBuilder in the declaration of the NavigationSplitView initializer. You don't need SidebarViewFunc
or DetailViewFunc
any more. Usually, separate closures go on separate lines:
struct ContentView: View {
var body: some View {
NavigationSplitView (
sidebar: { SidebarView() },
detail: { DetailView() }
)
}
}
This works fine, but most code examples won't look like this. The Swift developers were very proud of how much typing you don't have to do. If the last parameter to a function is a closure, you can omit its name and move it outside the parentheses:
struct ContentView: View {
var body: some View {
NavigationSplitView (
sidebar: { SidebarView() } )
{ DetailView() }
}
}
this works too. However SwiftUI does quite a lot with closures, often views take multiple closure parameters, so some people thought it would be cool to extend this syntax to multiple trailing closures. See https://github.com/apple/swift-evolution/blob/main/proposals/0279-multiple-trailing-closures.md for more context. The rule here is that the first trailing closure can be unnamed, while the subsequent ones must be named. Which has the effect here of removing the visible "sidebar" label and reinstating the "detail" label. But because all the parameters are closures and were moved out of the parentheses, we can omit the parentheses, grounds for rejoicing in some circles. So now the ContentView can look like this:
struct ContentView: View {
var body: some View {
NavigationSplitView {
SidebarView() }
detail: {
DetailView() }
}
}
Xcode's code completion suggestions suggest the explicitly labelled parameter version, not the multiple trailing closure syntax version, which is unhelpful. And if you try to translate from Xcode's suggestion to trailing closure syntax, and forget to delete a comma, you'll see five compiler errors, none of which tell you that you have a comma which shouldn't be there.
struct ContentView: View {
var body: some View {
NavigationSplitView {
SidebarView() }, // added (and unnecessary) comma
detail: {
DetailView() }
}
}
if that bothers you, I'd encourage you to file a bug. Swift error reporting has improved since the beginning, but the language has also become more and more complex.
Stick at it. Eventually you'll get used to it, and know what to look for when Swift kicks out an error you cannot understand.