NavigationLink isActive deprecated - swiftui

In the new version of iOS and Xcode
NavigationLink(isActive: , destination: , label: )
is deprecated.
How do we control the status of NavigationLink then?

Old/Deprecated way to navigate:
#State private var readyToNavigate : Bool = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: MyTargetView(), isActive: $readyToNavigate, label: {Text("Navigate Link")})
Button {
//Code here before changing the bool value
readyToNavigate = true
} label: {
Text("Navigate Button")
}
}
.navigationTitle("Navigation")
}
}
New way to navigate
#State private var readyToNavigate : Bool = false
var body: some View {
NavigationStack {
VStack {
Button {
//Code here before changing the bool value
readyToNavigate = true
} label: {
Text("Navigate Button")
}
}
.navigationTitle("Navigation")
.navigationDestination(isPresented: $readyToNavigate) {
MyTargetView()
}
}
}

Use new NavigationLink(value:label:) as specified. By putting corresponding value into NavigationPath you activate a link.
See more for example in this topic

Check out Apple's migration docs:
https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types

The isActive implementation of NavigationLink is deprecated in iOS 16. Instead, you should use the new NavigationLink(value:) along with .navigationDestination with NavigationStack.

Related

Adding a Navigation Link inside a menu [duplicate]

Can you use a NavigationLink as a Menu's item in swiftUI?
It seems to do just nothing:
Menu {
NavigationLink(destination: Text("test1")) {
Text("item1")
}
NavigationLink(destination: Text("test2")) {
Text("item2")
}
} label: {
Text("open menu")
}
In case it is meant to not work as tried above, is there an alternative way of achiving the intended reult?
init(destination:isActive:label:) is deprecated since iOS 16
'init(destination:isActive:label:)' was deprecated in iOS 16.0: use
NavigationLink(value:label:) inside a NavigationStack or
NavigationSplitView
NavigationLink should be inside NavigationView hierarchy. The Menu is outside navigation view, so put buttons inside menu which activate navigation link placed inside navigation view, eg. hidden in background.
Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1)
struct DemoNavigateFromMenu: View {
#State private var navigateTo = ""
#State private var isActive = false
var body: some View {
NavigationView {
Menu {
Button("item1") {
self.navigateTo = "test1"
self.isActive = true
}
Button("item2") {
self.navigateTo = "test2"
self.isActive = true
}
} label: {
Text("open menu")
}
.background(
NavigationLink(destination: Text(self.navigateTo), isActive: $isActive) {
EmptyView()
})
}
}
}
I can say that Asperi's answer is great solution. It helped a lot. But we need a custom view to hold a reference inside the destination property right? not a string.
#State var navigateTo: AnyView?
#State var isNavigationActive = false
We can hold a reference AnyView type and then call the view like this:
Menu {
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create an Item", systemImage: "doc")
}
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create a category", systemImage: "folder")
}
} label: {
Label("Add", systemImage: "plus")
}
For more detail please see this post:
https://developer.apple.com/forums/thread/119583

SwiftUI: How to push to new view from Menu Buttons [duplicate]

Can you use a NavigationLink as a Menu's item in swiftUI?
It seems to do just nothing:
Menu {
NavigationLink(destination: Text("test1")) {
Text("item1")
}
NavigationLink(destination: Text("test2")) {
Text("item2")
}
} label: {
Text("open menu")
}
In case it is meant to not work as tried above, is there an alternative way of achiving the intended reult?
init(destination:isActive:label:) is deprecated since iOS 16
'init(destination:isActive:label:)' was deprecated in iOS 16.0: use
NavigationLink(value:label:) inside a NavigationStack or
NavigationSplitView
NavigationLink should be inside NavigationView hierarchy. The Menu is outside navigation view, so put buttons inside menu which activate navigation link placed inside navigation view, eg. hidden in background.
Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1)
struct DemoNavigateFromMenu: View {
#State private var navigateTo = ""
#State private var isActive = false
var body: some View {
NavigationView {
Menu {
Button("item1") {
self.navigateTo = "test1"
self.isActive = true
}
Button("item2") {
self.navigateTo = "test2"
self.isActive = true
}
} label: {
Text("open menu")
}
.background(
NavigationLink(destination: Text(self.navigateTo), isActive: $isActive) {
EmptyView()
})
}
}
}
I can say that Asperi's answer is great solution. It helped a lot. But we need a custom view to hold a reference inside the destination property right? not a string.
#State var navigateTo: AnyView?
#State var isNavigationActive = false
We can hold a reference AnyView type and then call the view like this:
Menu {
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create an Item", systemImage: "doc")
}
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create a category", systemImage: "folder")
}
} label: {
Label("Add", systemImage: "plus")
}
For more detail please see this post:
https://developer.apple.com/forums/thread/119583

Navigate with tap using NavigationLink is broken when onLongPress modifier applied

I want to do two things. Firstly, I want to navigate to SpaceView if I tap on 'TileCell'.
NavigationLink(destination: SpaceView(space: space)) {
TileCell(
image: image,
text: space.name!,
detailText: nil,
isFaded: space.isComplete
)
}
.buttonStyle(PlainButtonStyle())
}
This works great.
But I also want to long press on TileCell to trigger a different action.
NavigationLink(destination: SpaceView(space: space)) {
TileCell(
image: image,
text: space.name!,
detailText: nil,
isFaded: space.isComplete
)
.onLongPressGesture {
action()
}
}
.buttonStyle(PlainButtonStyle())
}
The long-press gesture works, but I can no longer navigate to SpaceView by tapping.
Any help getting both to work would be appreciated.
I would suggest manually triggering the NavigationLink with isActive Binding in the onTapGesture. Then you can handle on tap and long press.
struct ContentView: View {
#State var outputText: String = ""
#State var isActive : Bool = false
var body: some View {
NavigationView {
NavigationLink(destination: SpaceView(), isActive: $isActive) { //<< here use isActive
TileCell()
.onTapGesture {
isActive = true //<< activate navigation link manually
}
.onLongPressGesture {
print("Long press") //<< long press action here
}
}
}
}
}
Building off davidev's answer, here's a version that works with multiple NavigationLinks created with ForEach
struct ContentView: View {
#State var tileTextArray = ["Tile 1", "Tile 2", "Tile 3"]
#State var isActiveArray = [false, false, false]
var body: some View {
NavigationView {
ForEach(tileTextArray.indices, id: \.self) { index in
NavigationLink(destination: SpaceView(), isActive: $isActiveArray[index]) { //<< here use isActive
TileCell()
.onTapGesture {
isActiveArray[index] = true //<< activate navigation link manually
}
.onLongPressGesture {
print("Long press") //<< long press action here
}
}
}
}
}
}

How can I use multiple fullScreenCover in IOS14

I want to present the two destinations view in full screen mode from a single view.
Below is a sample of my code. Seem that the function only works for single presentation, if I have a second fullScreenCover defined, the first fullScreenCover didn't work properly.Is that any workaround at this moment?
import SwiftUI
struct TesFullScreen: View {
init(game : Int){
print(game)
}
var body: some View {
Text("Full Screen")
}
}
ContentView
import SwiftUI
struct ContentView: View {
#State var showFullScreen1 : Bool = false
#State var showFullScreen2 : Bool = false
var body: some View {
NavigationView {
VStack {
Spacer()
Button(action: { self.showFullScreen1 = true }) {
Text("Show Full Screen 1")
}
Button(action: { self.showFullScreen2 = true }) {
Text("Show Full Screen 2")
}
Spacer()
}
.navigationBarTitle("TextBugs", displayMode: .inline)
}
.fullScreenCover(isPresented: self.$showFullScreen1){
TesFullScreen(game: 1)
}
.fullScreenCover(isPresented: self.$showFullScreen2){
TesFullScreen(game: 2)
}
}
}
Not always the accepted answer works (for example if you have a ScrollView with subviews (cells in former days) which holds the buttons, that set the navigational flags).
But I found out, that you also can add the fullScreen-modifier onto an EmptyView. This code worked for me:
// IMPORTANT: Has to be within a container (e.g. VStack, HStack, ZStack, ...)
if myNavigation.flag1 || myNavigation.flag2 {
EmptyView().fullScreenCover(isPresented: $myNavigation.flag1)
{ MailComposer() }
EmptyView().fullScreenCover(isPresented: $myNavigation.flag2)
{ RatingStore() }
}
Usually some same modifier added one after another is ignored. So the simplest fix is to attach them to different views, like
struct FullSContentView: View {
#State var showFullScreen1 : Bool = false
#State var showFullScreen2 : Bool = false
var body: some View {
NavigationView {
VStack {
Spacer()
Button(action: { self.showFullScreen1 = true }) {
Text("Show Full Screen 1")
}
.fullScreenCover(isPresented: self.$showFullScreen1){
Text("TesFullScreen(game: 1)")
}
Button(action: { self.showFullScreen2 = true }) {
Text("Show Full Screen 2")
}
.fullScreenCover(isPresented: self.$showFullScreen2){
Text("TesFullScreen(game: 2)")
}
Spacer()
}
.navigationBarTitle("TextBugs", displayMode: .inline)
}
}
}
Alternate is to have one .fullScreenCover(item:... modifier and show inside different views depending on input item.
The only thing that worked for me was the answer in this link:
https://forums.swift.org/t/multiple-sheet-view-modifiers-on-the-same-view/35267
Using the EmptyView method or other solutions always broke a transition animation on one of the two presentations. Either transitioning to or from that view and depending on what order I chose them.
Using the approach by Lantua in the link which is using the item argument instead of isPresented worked in all cases:
enum SheetChoice: Hashable, Identifiable {
case a, b
var id: SheetChoice { self }
}
struct ContentView: View {
#State var sheetState: SheetChoice?
var body: some View {
VStack {
...
}
.sheet(item: $sheetState) { item in
if item == .a {
Text("A")
} else {
Text("B")
}
}
}
}
The sheetState needs to be optional for it to work.

Is it possible for a NavigationLink to perform an action in addition to navigating to another view?

I'm trying to create a button that not only navigates to another view, but also run a function at the same time. I tried embedding both a NavigationLink and a Button into a Stack, but I'm only able to click on the Button.
ZStack {
NavigationLink(destination: TradeView(trade: trade)) {
TradeButton()
}
Button(action: {
print("Hello world!") //this is the only thing that runs
}) {
TradeButton()
}
}
You can use .simultaneousGesture to do that. The NavigationLink will navigate and at the same time perform an action exactly like you want:
NavigationLink(destination: TradeView(trade: trade)) {
Text("Trade View Link")
}.simultaneousGesture(TapGesture().onEnded{
print("Hello world!")
})
You can use NavigationLink(destination:isActive:label:). Use the setter on the binding to know when the link is tapped. I've noticed that the NavigationLink could be tapped outside of the content area, and this approach captures those taps as well.
struct Sidebar: View {
#State var isTapped = false
var body: some View {
NavigationLink(destination: ViewToPresent(),
isActive: Binding<Bool>(get: { isTapped },
set: { isTapped = $0; print("Tapped") }),
label: { Text("Link") })
}
}
struct ViewToPresent: View {
var body: some View {
print("View Presented")
return Text("View Presented")
}
}
The only thing I notice is that setter fires three times, one of which is after it's presented. Here's the output:
Tapped
Tapped
View Presented
Tapped
NavigationLink + isActive + onChange(of:)
// part 1
#State private var isPushed = false
// part 2
NavigationLink(destination: EmptyView(), isActive: $isPushed, label: {
Text("")
})
// part 3
.onChange(of: isPushed) { (newValue) in
if newValue {
// do what you want
}
}
This works for me atm:
#State private var isActive = false
NavigationLink(destination: MyView(), isActive: $isActive) {
Button {
// run your code
// then set
isActive = true
} label: {
Text("My Link")
}
}
Use NavigationLink(_:destination:tag:selection:) initializer and pass your model's property as a selection parameter. Because it is a two-way binding, you can define didset observer for this property, and call your function there.
struct ContentView: View {
#EnvironmentObject var navigationModel: NavigationModel
var body: some View {
NavigationView {
List(0 ..< 10, id: \.self) { row in
NavigationLink(destination: DetailView(id: row),
tag: row,
selection: self.$navigationModel.linkSelection) {
Text("Link \(row)")
}
}
}
}
}
struct DetailView: View {
var id: Int;
var body: some View {
Text("DetailView\(id)")
}
}
class NavigationModel: ObservableObject {
#Published var linkSelection: Int? = nil {
didSet {
if let linkSelection = linkSelection {
// action
print("selected: \(String(describing: linkSelection))")
}
}
}
}
It this example you need to pass in your model to ContentView as an environment object:
ContentView().environmentObject(NavigationModel())
in the SceneDelegate and SwiftUI Previews.
The model conforms to ObservableObject protocol and the property must have a #Published attribute.
(it works within a List)
I also just used:
NavigationLink(destination: View()....) {
Text("Demo")
}.task { do your stuff here }
iOS 15.3 deployment target.