Swiftui Navigationview Starting Inside Itself

SwiftUI NavigationLink pops out by itself

Looks like a bug when there is exactly 2 NavigationLinks.
If you add another empty link it goes away:

NavigationLink(destination: EmptyView(), label: {})

More details: https://forums.swift.org/t/14-5-beta3-navigationlink-unexpected-pop/45279

SwiftUI NavigationView pops by itself

You can replace \.self with \.id:

ForEach(model.someRObj, id: \.id) { obj in
Heh(obj: obj.obj, pr: obj.prop)
}

Then every object will be identified by id and the ForEach loop will only refresh when the id is changed.

SwiftUI NavigationView pop itself when a datasource is finished loading

This happens because you're specifying id as item itself, and when list updated there's no original item anymore, so it closes

If you just wanna modify items without adding/removing/reordering, you can make index your item id:

NavigationView {
List(viewModel.dataSource.indices, id: \.self) { i in
let item = viewModel.dataSource[i]
NavigationLink(destination: Text("\(item)")) {
Text("\(item)")
.padding()
}
}
}

But with more complex data you need to have your items Identifiable with unique ids, and you won't have such problem. Check out this example:

struct ContentView: View {
@ObservedObject var viewModel = ViewModel()

var body: some View {
NavigationView {
List(viewModel.dataSource) { item in
NavigationLink(destination: Text("\(item.value)")) {
Text("\(item.value)")
.padding()
}
}
}
}
}

class ViewModel: ObservableObject {
@Published private(set) var dataSource: [Item] = [1, 2, 3, 4, 5]

init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [self] in // simulate calling webservice
// you're modifying value but id stays the same
self.dataSource[0].value = 99
}
}
}

struct Item: Identifiable, ExpressibleByIntegerLiteral {
let id = UUID()
var value: Int

init(integerLiteral value: IntegerLiteralType) {
self.value = value
}
}

SwiftUI pushed View through Navigationview pops back after dismissing sheet

Currently placing a NavigationLink in the navigationBarItems may cause some issues.

Try removing the NavigationLink from navigationBarItems and put it the background:

struct homeView: View {
@State var showSearch: Bool = false
var body: some View {
NavigationView {
Text("home")
// move `NavigationLink` outside `navigationBarItems`
.background(NavigationLink("", destination: SearchContentView(), isActive: $showSearch))
.navigationBarTitle("", displayMode: .inline)
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarItems(trailing: HStack {
Button("search", action: {
showSearch.toggle()
})
})
}
}
}

SwiftUI: NavigationLink pops back when variable is updated

I had the same problem and managed to solve it today. Try adding the option .navigationViewStyle(.stack) to your NavigationView

struct ContentView: View {
var body: some View {
NavigationView {
//All your content and navigation links
}.navigationViewStyle(.stack)
}
}

SwiftUI: How can I determine if a view is presented in a NavigationView, Sheet, or is the root?

You can use SwiftUI-Introspect, used to "Introspect underlying UIKit components from SwiftUI".

Here is a working example of what you are looking for. It is an interactive example, so you can click through the different modes.

import Introspect
import SwiftUI

/* ... */

struct ContentView: View {

@State private var testing = 1
private let thingsToTest = 3

var body: some View {
VStack {
Picker("Testing", selection: $testing) {
ForEach(1 ... thingsToTest, id: \.self) { index in
Text("\(index)")
.tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())

Divider()

Spacer()

switch testing {
case 1:
PresentationReader { kind in
Text("Hello! Kind: \(kind.rawValue)")
}

case 2:
NavigationView {
PresentationReader { kind in
Text("Hello! Kind: \(kind.rawValue)")
}
}

case 3:
Text("Parent")
.sheet(isPresented: .constant(true)) {
PresentationReader { kind in
Text("Hello! Kind: \(kind.rawValue)")
}
}

default:
fatalError("Unavailable")
}

Spacer()
}
}
}
enum Kind: String {
case navigationView
case root
case sheet
}

struct PresentationReader<Content: View>: View {
typealias PresentedContent = (Kind) -> Content

@State private var kind: Kind = .root
private let content: PresentedContent

init(@ViewBuilder content: @escaping PresentedContent) {
self.content = content
}

var body: some View {
content(kind)
.presentationReader(kind: $kind)
}
}

extension View {
func presentationReader(kind: Binding<Kind>) -> some View {
self
.introspectViewController { vc in
let rootVC = UIApplication.shared.windows.first?.rootViewController
let isRoot = vc === rootVC
var isHosted: Bool { Introspect.findHostingView(from: vc.view) != nil }

if isRoot {
kind.wrappedValue = .root
} else if isHosted {
kind.wrappedValue = .navigationView
} else {
kind.wrappedValue = .sheet
}
}
}
}

It works by getting the current view controller the view is in.

  • If the class reference of the root view controller is the same as the current root view controller, this is the root view (meaning it isn't embedded in a NavigationView or .sheet(...)).
  • If this is not the root, we then check if this view is embedded in a hosting view. If it is, it is in a NavigationView.
  • If the view is neither the root or in a NavigationView, it is therefore in a .sheet(...).

This is now what your CustomNavigationBar will look like with these 3 changes:

struct CustomNavigationBar<Content>: View where Content: View {

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
@State private var kind: Kind = .root // <--- CHANGE #1

private let title: LocalizedStringKey
private let content: (() -> Content)?

private var backButton: AnyView? {

let button = Button(action: { self.presentationMode.wrappedValue.dismiss() }) {
// custom image extension, just resolves to a back icon
Image.Icons.arrowBack
}

if kind == .navigationView { // <--- CHANGE #2
return AnyView(button)
} else {
return nil
}
}

public init(_ title: LocalizedStringKey, content: (() -> Content)? = nil) {
self.title = title
self.content = content
}

var body: some View {
VStack {
content?()
.presentationReader(kind: $kind) // <--- CHANGE #3

Divider().foregroundColor(.gray)
}.navigationBarTitle(title, displayMode: .large)
.frame(minHeight: 96)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton)
}
}


Related Topics



Leave a reply



Submit