My SwiftUI View doesn't update navigation title for third level - swift

I have prepared a simple example with three levels:
struct Element: Identifiable {
let id = UUID()
let text: String
init(text: String) {
self.text = text
}
}
struct FirstView: View {
#State var elements = [Element(text: "a"), Element(text: "b")]
var body: some View {
NavigationView {
List(elements) { element in
NavigationLink {
SecondView()
} label: {
VStack(alignment: .leading, spacing: 3, content: {
Text(element.text)
.font(.system(size: 12))
.foregroundColor(.white)
})
}
}
.navigationTitle("First")
}
}
}
struct SecondView: View {
#State var elements = [Element(text: "1"), Element(text: "2")]
var body: some View {
NavigationView {
List(elements) { element in
NavigationLink {
ThirdView()
} label: {
VStack(alignment: .leading, spacing: 3, content: {
Text(element.text)
.font(.system(size: 12))
.foregroundColor(.white)
})
}
}
.navigationTitle("Second")
}
}
}
struct ThirdView: View {
#State var elements = [Element(text: "+"), Element(text: "-")]
var body: some View {
NavigationView {
List(elements) { element in
NavigationLink {
SecondView()
} label: {
VStack(alignment: .leading, spacing: 3, content: {
Text(element.text)
.font(.system(size: 12))
.foregroundColor(.white)
})
}
}
.navigationTitle("Third")
}
}
}
And this is what happens in Apple Watch when I go to the next levels:
As you can see for third view,navigation title was not updated. Why?
Is there also something wrong with rendering? When I swipe down second list it ends up like:

You just need to remove NavigationView from around the SecondView and ThirdView, i.e.
struct SecondView: View {
#State var elements = [Element(text: "1"), Element(text: "2")]
var body: some View {
List(elements) { element in
NavigationLink {
ThirdView()
} label: {
VStack(alignment: .leading, spacing: 3, content: {
Text(element.text)
.font(.system(size: 12))
.foregroundColor(.white)
})
}
}
.navigationTitle("Second")
}
}
If you're building for watchOS 9.0+, you should probably use the new NavigationStack

Related

SwiftUI Recursive View for Continued Fraction

I wrote a ContinuedFractionView to draw continued fraction structure on my (macos) app.
It works fine when my CF list is relatively short. However, when the list gets a bit longer ( 7, 8, ... ), UI window nearly stops (unresponsive for minutes).
Why? and how could I improve my view?
import SwiftUI
struct ContinuedFractionView: View {
let continuedFraction: [Int]
var body: some View {
HStack(alignment: .top) {
if continuedFraction.count > 1 {
VStack {
Text("")
Text(String(continuedFraction[0]))
}
VStack {
Text("")
Text("+")
}
VStack {
Text("1")
Divider()
.background(Color.black)
ContinuedFractionView(continuedFraction: Array(continuedFraction[1...]))
}
} else {
Text(String(continuedFraction[0]))
}
}
}
}
struct ContinuedFractionView_Previews: PreviewProvider {
static var previews: some View {
ContinuedFractionView(continuedFraction: [1,2,3,4,5,6,7])
}
}
Does it have to be done recursively? If not, it should be pretty straightforward to draw this:
struct ContentView: View {
let fracs = [1,2,3,4,5]
var body: some View {
List {
Section("Mine") {
NonRecursive(fracs: fracs)
}
Section("Yours") {
ContinuedFractionView(continuedFraction: fracs)
}
}
.listStyle(.plain)
}
}
struct NonRecursive: View {
let fracs: [Int]
var body: some View {
VStack(alignment: .leading) {
ForEach(Array(fracs.dropLast().enumerated()), id: \.1) { (index, frac) in
HStack(alignment: .top) {
HStack {
Spacer(minLength: 0)
VStack(alignment: .trailing) {
Text("")
Text(frac, format: .number) + Text(" +")
}
}
.frame(width: CGFloat(index + 1) * 25)
VStack {
Text("1")
Divider()
.background(Color.black)
if let last = fracs.last, index == fracs.count - 2 {
Text(last, format: .number)
}
}
}
}
}
}
}

How debug my code in List with NavigationView

On the Canvas my code start correctly, but simulator send message about bug
import SwiftUI
struct ContentView: View {
//1
var persons: [Member] = []
var body: some View {
NavigationView() {
break point stopped here
List(persons) { person in
NavigationLink(destination: {
MemberDetail(name: person.name, headline: person.headline, bio: person.bio)
}, label: {
HStack {
Image(person.imageName).cornerRadius(40.0)
VStack(alignment: .leading) {
Text(person.name)
Text(person.headline)
.font(.subheadline)
.foregroundColor(.gray)
}
}
})
}
.navigationTitle(Text("Persons"))
}
}
}
expectation
reality
Code struct of MemberDetails
struct MemberDetail: View {
var name: String
var headline: String
var bio: String
var body: some View {
VStack {
Image(name)
.clipShape(Circle())
.overlay(Circle().stroke(Color.orange, lineWidth: 4))
.shadow(radius: 10)
Text(name)
.font(.title)
Text(headline)
.font(.subheadline)
Divider()
Text(bio)
.font(.headline)
.multilineTextAlignment(.center)
.lineLimit(50)
}
.padding(20)
.navigationBarTitle(Text(name), displayMode: .inline)
}
}
break points get error. I run my project on iphone 7 and simulator, but this not give solution
I completely rewrote the code according to the guides from apple and it all worked
struct LandmarkList: View {
var body: some View {
NavigationView {
List(landmarks) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
LandmarkRow(landmark: landmark)
}
}
.navigationTitle("Landmarks")
}
}

Why My second view cannot jump back to the root view properly

My App currently has two pages, first page has a circle plus button which could lead us to a second page. Basically, I have a save button which after clicking it, we could get back to the rood page. I followed this link for going back to root view. I tried the most up voted code, his code works perfectly. I reduced his code to two scene (basically the same scenario as mine), which also works perfectly. But then I don't know why my own code, pasted below, doesn't work. Basically my way of handling going back to root view is the same as the one in the link.
//
// ContentView.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/22/20.
//
import SwiftUI
import UIKit
#if canImport(UIKit)
extension View {
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
#endif
struct ContentView: View {
#EnvironmentObject private var fridge : Fridge
private var dbStartWith=0;
#State var pushed: Bool = false
#State private var selection = 1;
#State private var addFood = false;
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
NavigationLink(destination: AddFoodView(pushed: self.$pushed),isActive: self.$pushed) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
}.isDetailLink(false) )
}
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(1)
Text("random tab")
.font(.system(size: 30, weight: .bold, design: .rounded))
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("profile")
}
.tag(0)
}.environmentObject(fridge)
}
}
struct FoodView: View{
var body: some View{
NavigationView{
Text("food destination view ");
}
}
}
struct AddFoodView: View{
#Binding var pushed : Bool
#EnvironmentObject private var fridgeView : Fridge
#State private var name = ""
#State private var count : Int = 1
#State private var category : String = "肉类";
#State var showCategory = false
#State var showCount = false
var someNumberProxy: Binding<String> {
Binding<String>(
get: { String(format: "%d", Int(self.count)) },
set: {
if let value = NumberFormatter().number(from: $0) {
self.count = value.intValue;
}
}
)
}
var body: some View{
ZStack{
NavigationView{
VStack{
Button (action: {
self.pushed = false ;
//let tempFood=Food(id: fridgeView.index!,name: name, count: count, category: category);
//fridgeView.addFood(food: tempFood);
} ){
Text("save").foregroundColor(Color.blue).font(.system(size: 18,design: .default)) }
}.navigationBarTitle("Three")
}
ZStack{
if self.showCount{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCount=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $count,label: EmptyView()) {
ForEach(1..<100){ number in
Text("\(number)").tag("\(number)")
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
.offset(x:10,y:-10)
Spacer()
}
if self.showCategory{
let categoryArr = ["肉类","蔬菜类","饮料类","调味品类"]
ZStack{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCategory=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $category,label: EmptyView()) {
ForEach(0..<categoryArr.count){ number in
Text(categoryArr[number]).tag(categoryArr[number])
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
Spacer()
}.offset(x:10,y:20)
}
}
}.animation(.easeInOut)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you read my code carefully, there are some variables are missing referencing. That's because I pasted part of the code that relates to my issue.
Food Class
//
// Food.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/23/20.
//
import Foundation
class Food: Identifiable {
init(id:Int, name: String, count: Int, category: String){
self.id=id;
self.name=name;
self.count=count;
self.category=category;
}
var id: Int
var name: String
var count: Int
var category: String
}
Fridge class
//
// Fridge.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/27/20.
//
import Foundation
class Fridge: ObservableObject{
init(){
db=DBhelper();
let result = setIndex(database: db!);
self.index = result.1;
self.container=result.0;
}
var db:DBhelper?
var index : Int?
#Published var container : [Food]?;
func setIndex(database: DBhelper) -> ([Food],Int){
let foodList : [Food] = database.read();
var index=0;
for food in foodList{
index = max(food.id,index);
}
return (foodList,(index+1));
}
func updateindex(index: inout Int){
index=index+1;
}
func testExist(){
if let data = db {
print("hi")
}
else{
print("doesnt exist")
}
}
func addFood(food:Food){
self.db!.insert(id: self.index!, name: food.name, count:food.count, category: food.category);
self.container!.append(food);
}
}
Because you implemented a new NaviagtionView in AddFoodView. Simply remove this and it should work. Look at the link you provided. There is no NavigationView in the child.
Correct me if Im wrong but the core code parts here that produce this issue are as follows:
Here you start:
struct ContentView: View {
#State var pushed: Bool = false
// Deleted other vars
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
// Here you navigate to the child view
NavigationLink(destination: AddFoodView(pushed: self.$pushed),isActive: self.$pushed) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
}.isDetailLink(false) )
}
Here you land and want to go back to root:
struct AddFoodView: View{
#Binding var pushed : Bool
// Deleted the other vars for better view
var body: some View{
ZStack{
NavigationView{ // <-- remove this
VStack{
Button (action: {
// here you'd like to go back
self.pushed = false;
} ){
Text("save").foregroundColor(Color.blue).font(.system(size: 18,design: .default)) }
}.navigationBarTitle("Three")
}
For the future:
I have the feeling you might have troubles with the navigation in general.
Actually it is really simple:
You implement one NavigationView at the "root" / start of your navigation.
From there on you only use NavigationLinks to go further down to child pages. No NavigationView needed anymore.

SwiftUI TextField Bug

I'm having trouble with a bug in the TextField Keyboard.
When I tap the corresponding textField, the Keyboard appears, but something like a white View appears together and the TextField is hidden. (See image)
Xcode12 Iphone11-Ios13.5 Simulator doesn't have this bug, but Ios14 does. Does anyone know a solution?
struct PlaceholderTextField: View {
var placeholderTxt: String
var keyboardType: UIKeyboardType?
#Binding var text: String
var body: some View {
ZStack(alignment: .trailing) {
VStack(alignment: .leading) {
VStack {
if self.keyboardType != nil {
TextField(self.placeholderTxt, text: $text)
.autocapitalization(.none)
.padding(20)
.keyboardType(self.keyboardType!)
} else {
TextField(self.placeholderTxt, text: $text)
.autocapitalization(.none)
.padding(20)
}
}
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 15))
.padding()
}
}
}
}
struct LoginView: View {
#ObservedObject(initialValue: LoginViewModel()) var loginController: LoginViewModel
#EnvironmentObject var userData: UserData
#State var emailLogin: Bool = true
#Environment(\.presentationMode) var presentation
#Binding var rootIsActive: Bool
var body: some View {
ZStack {
GeometryReader { bodyView in
ZStack {
Color.backgroundColor.edgesIgnoringSafeArea(.all)
VStack(spacing: 0) {
SwitchAccountIDButton(emailLogin: self.$emailLogin)
self.userIdTextField
self.passwordTextField
Button(action: {
self.userData.isLoading = true
if self.emailLogin {
self.loginController.singin(self.loginController.email, self.loginController.password, self.emailLogin)
} else {
self.loginController.singin(self.loginController.phoneNumber, self.loginController.password, self.emailLogin)
}
}) {
ButtonView(title: "ログイン", fontColor: .white, bgColor: Color.primaryColor, width: bodyView.size.width * 0.9)
.accessibility(identifier: "login_login_button")
}
NavigationLink(destination: ReissuePassword(shouldPopToRootView: self.$rootIsActive)) {
Text("パスワードを忘れた場合")
.underline()
.foregroundColor(Color.primaryColor)
.padding(.top)
}.isDetailLink(false)
Spacer()
}
}
}
.navigationBarTitle("ログイン")
.navigationBarBackButtonHidden(true)
.navigationBarItems(trailing: VStack {
Button(action: {
self.presentation.wrappedValue.dismiss()
}, label: { Text("キャンセル").foregroundColor(Color.primaryColor).fontWeight(.regular) })
})
}
}
var userIdTextField: some View {
VStack {
if self.emailLogin {
PlaceholderTextField(placeholderTxt: "メールアドレス",keyboardType: .default ,text: self.$loginController.email)
.accessibility(identifier: "login_mailaddress_textfield")
} else {
PlaceholderTextField(placeholderTxt: "電話番号", keyboardType: .phonePad, text: self.$loginController.phoneNumber)
.accessibility(identifier: "login_phonenumber_textfield")
}
}
}
}

List Sections do not work correctly if .navigationBarItems is present

How to have both a button in navigation bar and a list with sections?
Here is a code with .navigationBarItems:
struct AllMatchesView: View {
#Environment(\.managedObjectContext) var moc
#State var events = EventData()
var body: some View {
NavigationView {
List{
ForEach(events.sections) { section in
Section(header: Text(section.title)) {
ForEach(section.matches) { match in
Text("Row")
}
}
.navigationBarTitle("Title")
.navigationBarItems(trailing:
NavigationLink(destination: AddMatchView().environment(\.managedObjectContext, self.moc)) {
Image(systemName: "plus")
.resizable()
.frame(width: 22, height: 22)
.padding(.horizontal)
}
.padding(.leading)
)
}
}.onAppear(){
self.events = EventData()
}
}
}
}
Without .navigationBarItems:
struct AllMatchesView: View {
#Environment(\.managedObjectContext) var moc
#State var events = EventData()
var body: some View {
NavigationView {
List{
ForEach(events.sections) { section in
Section(header: Text(section.title)) {
ForEach(section.matches) { match in
Text("Row")
}
}
.navigationBarTitle("Title")
}
}.onAppear(){
self.events = EventData()
}
}
}
}
Result with .navigationBarItems:
Result without .navigationBarItems:
Just move those modifiers out of dynamic content, otherwise you try to include duplicated bar items for every section, that seems makes SwiftUI engine crazy.
var body: some View {
NavigationView {
List{
ForEach(events.sections) { section in
Section(header: Text(section.title)) {
ForEach(section.matches) { match in
Text("Row")
}
}
}
}
.navigationBarTitle("Title")
.navigationBarItems(trailing:
NavigationLink(destination: AddMatchView().environment(\.managedObjectContext, self.moc)) {
Image(systemName: "plus")
.resizable()
.frame(width: 22, height: 22)
.padding(.horizontal)
}
.padding(.leading)
)
.onAppear(){
self.events = EventData()
}
}
}