I have code like this:
List(datalist) { data in
NavigationLink(destination: View1(data: data).headless()) {
Text(data.name)
}
}
where headless() is a way to avoid all the default top views of a NavigationLink and its corresponding init:
extension View {
// https://www.hackingwithswift.com/forums/swiftui/removing-unwanted-and-unknown-whitespace-possibly-a-navigation-bar-above-a-view/7343
func headless() -> some View {
return self.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
What I want to do is have a View struct that can be a customized call to NavigationLink that always calls the headless() modifier. I have written this, copying from the declaration of NavigationLink:
struct SimpleNavLink<Destination, Label>: View where Label : View, Destination : View {
private let label: () -> Label
private let destination: () -> Destination
init(#ViewBuilder destination: #escaping () -> Destination, #ViewBuilder label: #escaping () -> Label) {
self.label = label
self.destination = destination
}
var body: some View {
NavigationLink(destination: destination().headless, label: label)
}
}
With that in place I changed the NavigationLink line to this:
SimpleNavLink(destination: View1(data: data)) {
But that gave me the error
Cannot convert value of type 'View1' to expected argument type '() -> Destination'
That was easy enough to solve by just wrapping the destination in { }:
SimpleNavLink(destination: { View1(data: data) } ) {
But WHY? I didn't have to do that for the NavigationLink. When I tried adding #autoclosure to the destination parameter, the compiler said that didn't go with #ViewBuilder
You said
I have written this, copying from the declaration of :
I assume you meant “the declaration of NavigationLink”. But your original code uses a NavigationLink.init that is declared like this:
#available(iOS, introduced: 13.0, deprecated: 100000.0, message: "Pass a closure as the destination")
#available(macOS, introduced: 10.15, deprecated: 100000.0, message: "Pass a closure as the destination")
#available(tvOS, introduced: 13.0, deprecated: 100000.0, message: "Pass a closure as the destination")
#available(watchOS, introduced: 6.0, deprecated: 100000.0, message: "Pass a closure as the destination")
public init(destination: Destination, #ViewBuilder label: () -> Label)
This version of init takes the Destination by value instead of as a function. It's also going to be deprecated at some point.
So, if you want to mimic the (eventually deprecated) syntax, you need to change your init to take the Destination by value instead of as a function. Note also that NavigationLink does not require #escaping closures, so perhaps you shouldn't either. Thus:
struct SimpleNavLink<Destination: View, Label: View>: View {
private let label: Label
private let destination: Destination
init(
destination: Destination,
#ViewBuilder label: () -> Label
) {
self.label = label()
self.destination = destination
}
var body: some View {
NavigationLink(
destination: destination.headless(),
label: { label }
)
}
}
Related
This question already has an answer here:
Swiftui How to pass a view that takes a parameter to another view that will set the parameter when calling the view
(1 answer)
Closed last month.
I am trying to make a NavigationLink and provide the destination in its init but I am receiving an error:
Type 'any View' cannot conform to 'View'
struct MenuButton: View {
let iconName: String
let destination: () -> any View
var body: some View {
NavigationLink { //Type 'any View' cannot conform to 'View'
destination()
} label: {
Image(systemName: iconName)
.foregroundColor(.pink)
.padding()
}
}
}
struct MenuBar: View {
var body: some View {
HStack {
MenuButton(iconName: "gearshape") {
//providing destination here
let user = User(firstName: "Mock", lastName: "Data", dateStarted: 142356345)
return HomeView(viewModel: HomeViewModel(user: user))
}
}
}
}
If I switch any View to some View in the destination declaration, I receive an error:
Property declares an opaque return type, but has no initializer expression from which to infer an underlying type
You just need to make MenuButton generic over some type (say V) that conforms to View:
struct MenuButton<V: View>: View {
let iconName: String
let destination: () -> V
var body: some View {
NavigationLink {
destination()
} label: {
Image(systemName: iconName)
.foregroundColor(.pink)
.padding()
}
}
}
Another option I found is wrapping destination() in AnyView:
struct MenuButton: View {
let iconName: String
let destination: () -> any View
var body: some View {
NavigationLink {
AnyView(destination())
} label: {
Image(systemName: iconName)
.foregroundColor(.pink)
.padding()
}
}
}
I am trying to replace a standard sheet modifier with a custom one that applies the same changes to the content of all sheets as to the main view (it can be useful for changing accent color, although there is a UIKit approach for it, but specifically I want to apply privacySensitive modifier to all sheets).
The code that creates the modifiers compiles ok:
import SwiftUI
struct SheetForItem<T, C>: ViewModifier where T: Identifiable, C: View {
var item: Binding<T?>
var onDismiss: (() -> Void)? = nil
var sheetContent: (T) -> C
func body(content: Content) -> some View {
content.sheet(item: item, onDismiss: onDismiss) {
sheetContent($0).privacySensitive()
}
}
}
extension View {
func appSheet<T, Content>(
item: Binding<T?>,
onDismiss: (() -> Void)? = nil,
content: #escaping (T) -> Content
) -> some View where T: Identifiable, Content: View {
modifier(SheetForItem(item: item, onDismiss: onDismiss, sheetContent: content))
}
}
Mostly it works, but some of the usages of appSheet in the chain of other modifiers instead of sheet do not compile with an error:
Type () cannot conform to View.
The example below doesn't compile (but it will compile if I replace appSheet with sheet):
import SwiftUI
enum Actions:Identifiable {
case action1
case action2
var id: Self { self }
}
struct AppSheetExample: View {
#State var showActions = false
#State private var action: Actions?
var body: some View {
Button { showActions = true } label: {
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
}
.confirmationDialog("Actions", isPresented: $showActions, titleVisibility: .visible) {
Button("Action 1") { action = .action2 }
Button("Action 2") { action = .action2 }
}
.appSheet(item: $action) { sheet in
switch sheet {
case .action1: Text("Action 1")
case .action2: Text("Action 2")
}
}
}
}
Thank you!
You need to mark your content closure with #ViewBuilder since you're not explicitly returning a View(i.e: return Text("Action 1")):
extension View {
func appSheet<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
#ViewBuilder content: #escaping () -> Content
) -> some View where Content: View {
modifier(SheetIsPresented(isPresented: isPresented, onDismiss: onDismiss, sheetContent: content))
}
func appSheet<T, Content>(
item: Binding<T?>,
onDismiss: (() -> Void)? = nil,
#ViewBuilder content: #escaping (T) -> Content
) -> some View where T: Identifiable, Content: View {
modifier(SheetForItem(item: item, onDismiss: onDismiss, sheetContent: content))
}
}
I have a custom NavigationLink in SwiftUI. Now I'm trying to add isActive to my customNavLink but I'm facing with Missing argument for parameter 'isActive' in call across all project. I want to make this isActive optional to use where I need it.
Do you know how could I resolve this issue?
This is My CustomNavLink
struct CusNavLink<Label: View, Destination: View>: View {
let destination: Destination
let label : Label
let isActive: Binding<Bool>
init(destination: Destination, isActive: Binding<Bool>, #ViewBuilder label: () -> Label) {
self.destination = destination
self.label = label()
self.isActive = isActive
}
var body: some View {
NavigationLink(
destination: CusNavContainer{
destination
}
.navigationBarHidden(true),
isActive: isActive,
label:{
label
})
}
}
If you want isActive to be Optional, you'd have to declare it as such in the initializer and properties. Then, you'd conditionally display a different NavigationLink initializer depending on if you have an isActive Binding to pass it:
struct CusNavLink<Label: View, Destination: View>: View {
let destination: Destination
let label : Label
let isActive: Binding<Bool>?
init(destination: Destination, isActive: Binding<Bool>? = nil, #ViewBuilder label: () -> Label) {
self.destination = destination
self.label = label()
self.isActive = isActive
}
var body: some View {
if let isActive = isActive {
NavigationLink(
destination: CusNavContainer {
destination
}
.navigationBarHidden(true),
isActive: isActive,
label:{
label
})
} else {
NavigationLink(
destination: CusNavContainer {
destination
}
.navigationBarHidden(true),
label:{
label
})
}
}
}
I want to put three links in a NavigationView.
First link: Displays a RoomView with a specific room ID, "room.A"
Second link: Displays a RoomView with a specific room ID, "room.B"
Third link: Displays a RoomView with a randomly generated room ID when the user clicks.
The following code does not work, because SwiftUI calls UUID().uuidString when it builds the NavigationView, and reuse it.
I want to generate a new random room ID when the user clicks the link, but I am not able to figure out how to do it. I am looking for a simple and natural way to achieve this.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: RoomView(roomId: "room.A")) {
Text("Enter Room A")
}
NavigationLink(destination: RoomView(roomId: "room.B")) {
Text("Enter Room B")
}
NavigationLink(destination: RoomView(roomId: UUID().uuidString)) {
Text("Create a new Room and enter")
}
}
}
}
}
struct RoomView: View {
let roomId: String
var body: some View {
Text("Room View \(roomId)")
}
}
I found a simple solution, but this exposes the weirdness of SwiftUI.
This code and the code above are logically identical, but behave differently because of the optimization of SwiftUI.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: RoomView(roomId: "room.A")) {
Text("Enter Room A")
}
NavigationLink(destination: RoomView(roomId: "room.B")) {
Text("Enter Room B")
}
NavigationLink(destination: NewRoomView()) {
Text("Create a new Room and enter")
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct NewRoomView: View {
var body: some View {
RoomView(roomId: UUID().uuidString)
}
}
That's because the navigation link is performed eagerly. I made a view to run the creation on tap.
import SwiftUI
struct LazyView<Content>: View where Content: View {
private let viewBuild: () -> Content
fileprivate init(_ viewBuild: #escaping () -> Content) {
self.viewBuild = viewBuild
}
init(_ viewBuild: #escaping #autoclosure () -> Content) {
self.init(viewBuild)
}
var body: some View {
viewBuild()
}
}
extension NavigationLink {
init<V>(lazyDestination: #escaping #autoclosure () -> V, #ViewBuilder label: () -> Label) where Destination == LazyView<V> {
self.init(destination: LazyView(lazyDestination), label: label)
}
init<V>(lazyDestination: #escaping #autoclosure () -> V, isActive: Binding<Bool>, #ViewBuilder label: () -> Label)
where Destination == LazyView<V> {
self.init(destination: LazyView(lazyDestination), isActive: isActive, label: label)
}
init<V, T>(lazyDestination: #escaping #autoclosure () -> V, tag: T, selection: Binding<T?>, #ViewBuilder label: () -> Label)
where T : Hashable, Destination == LazyView<V> {
self.init(destination: LazyView(lazyDestination), tag: tag, selection: selection, label: label)
}
}
extension NavigationLink where Label == Text {
init<V>(_ titleKey: LocalizedStringKey, lazyDestination: #escaping #autoclosure () -> V) where Destination == LazyView<V> {
self.init(titleKey, destination: LazyView(lazyDestination))
}
init<V, S>(_ title: S, lazyDestination: #escaping #autoclosure () -> V) where S : StringProtocol, Destination == LazyView<V> {
self.init(title, destination: LazyView(lazyDestination))
}
init<V>(_ titleKey: LocalizedStringKey, lazyDestination: #escaping #autoclosure () -> V, isActive: Binding<Bool>)
where Destination == LazyView<V> {
self.init(titleKey, destination: LazyView(lazyDestination), isActive: isActive)
}
init<V, S>(_ title: S, lazyDestination: #escaping #autoclosure () -> V, isActive: Binding<Bool>)
where S : StringProtocol, Destination == LazyView<V> {
self.init(title, destination: LazyView(lazyDestination), isActive: isActive)
}
init<V, T>(_ titleKey: LocalizedStringKey, lazyDestination: #escaping #autoclosure () -> V, tag: T, selection: Binding<T?>)
where T : Hashable, Destination == LazyView<V> {
self.init(titleKey, destination: LazyView(lazyDestination), tag: tag, selection: selection)
}
init<V, S, T>(_ title: S, lazyDestination: #escaping #autoclosure () -> V, tag: T, selection: Binding<T?>)
where S : StringProtocol, T : Hashable, Destination == LazyView<V> {
self.init(title, destination: LazyView(lazyDestination), tag: tag, selection: selection)
}
}
Then in your code, replace with
NavigationLink(lazyDestination: RoomView(roomId: UUID().uuidString)) {
Text("Create a new Room and enter")
}
I just migrated to use CoreDate instead of a simple collection. I'm using iOS13 beta 8 and Xcode11 beta 6.
struct BeaconList: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: fetchRequest(), animation: nil) var beacons: FetchedResults<Beacon>
static func fetchRequest() -> NSFetchRequest<Beacon> {
let request: NSFetchRequest<Beacon> = Beacon.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]
return request
}
func buildView(name: String, beacons: [Beacon]) -> AnyView {
return AnyView (
Section(header: Text(name)) {
ForEach(beacons) { beacon in
BeaconListEntry(beacon: beacon)
}
}
)
}
var body: some View {
TabView {
NavigationView {
List {
buildView(name: "MY BEACONS", beacons: beacons.filter { $0.isActive })
}
.navigationBarTitle("Beacons")
.listStyle(GroupedListStyle())
.navigationBarItems(trailing: addButton)
}
.tabItem {
Image(systemName: "antenna.radiowaves.left.and.right")
Text("Beacons")
}
}
}
and with BeaconListEntryas follows:
struct BeaconListEntry : View {
#Binding var beacon: Beacon
var body: some View {
HStack {
Text(verbatim: beacon.name!)
}
}
}
(Please ignore the forced unwrapped, it's just for testing purposes)
When I used this with collections before I rewrote, it worked, but now I get the message
Cannot invoke initializer for type 'ForEach<_, _, _>' with an argument list of type '([Beacon], #escaping (Binding<Beacon>) -> BeaconListEntry)'
and
1. Overloads for 'ForEach<_, _, _>' exist with these partially matching parameter lists: (Data, content: #escaping (Data.Element) -> Content), (Range<Int>, content: #escaping (Int) -> Content)
Any idea on where to look? Is this the correct way to use FetchedResults?
The BeaconListEntry initializer expects an argument having type Binding, so you should modify your call as follows:
BeaconListEntry(beacon: .constant(beacon))