SwiftUI中NavigationLink初始化简写语法为何顺序调换会报错?
要搞懂这个问题,核心得看SwiftUI给NavigationLink提供的初始化器重载,以及Swift的尾随闭包语法规则:
1. 第一种写法对应的初始化器
你写的第一个可行代码:
NavigationLink { LandmarkDetail(landmark: landmark) } label: { LandmarkRow(landmark: landmark) }
对应的是NavigationLink的这个初始化器:
init(destination: () -> Destination, @ViewBuilder label: () -> Label) where Label : View, Destination : View
Swift 5.3之后支持多重尾随闭包语法:当函数的第一个参数是闭包类型时,可以用无标签的尾随闭包传递,后续的闭包参数则必须显式带上参数标签。这里第一个尾随闭包对应destination参数(无标签,因为它是初始化器的第一个参数),label:标签的闭包对应第二个参数,完全符合语法规则,所以能正常编译。
2. 第二种写法为什么报错?
你尝试的代码:
NavigationLink { LandmarkRow(landmark: landmark) } destination: { LandmarkDetail(landmark: landmark) }
问题在于:不存在任何一个NavigationLink初始化器,是把label闭包作为第一个无标签参数,同时把destination闭包作为带标签的第二个参数。
编译器在匹配时,会优先找最接近的重载——比如这个接受View类型label和View类型destination的初始化器:
init(_ title: S, destination: Destination) where Label == Text, S : StringProtocol, Destination : View
它的第一个参数是View类型(比如Text),第二个参数是View类型的目标页面。但你传入的destination:后面是一个闭包() -> LandmarkDetail,而这个初始化器需要的是直接的View实例,不是闭包,所以编译器抛出错误:Type '() -> LandmarkDetail' cannot conform to View——意思是这个闭包类型无法转换成View类型。
总结
NavigationLink的初始化器设计中,只有把destination闭包放在第一个参数位置的重载,才支持你用「第一个无标签尾随闭包+后续带标签闭包」的写法。反过来把label放在第一个无标签尾随闭包的写法,没有对应的初始化器支持,因此会触发编译错误。
内容的提问来源于stack exchange,提问作者Kevin D.

