3

As you can see from the screenshot, the 'Language' tab appears twice.

First TabView tab appears twice

I've got the following code for HostingTabBar which is called on app startup:


struct HostingTabBar: View {
    
    private enum Tab: Hashable {
        case language
        case canvas
        case homework
        case test
        case more
    }
    
    @State private var selectedTab: Tab = .language
    
    var body: some View {
        TabView(selection: $selectedTab) {
        LanguageView()
            .tag(0)
            .tabItem {
                Text("Language")
                Image("language")
            }
        CanvasView()
            .tag(1)
            .tabItem {
                Text("Canvas")
                Image("canvas")
            }
        HomeworkView()
            .tag(2)
            .tabItem {
                Text("Homework")
                Image("homework")
            }
        TestView()
            .tag(3)
            .tabItem {
                Text("Test")
                Image("test")
            }
        MoreView()
            .tag(4)
            .tabItem {
                Text("More")
                Image("more")
            }
        }
        .accentColor(nil)
    }
}

struct HostingTabBar_Previews: PreviewProvider {
    static var previews: some View {
        HostingTabBar()
    }
}

LanguageView() is:

import CoreData

struct LanguageView: View {
    
    @State private var addLanguageIsPresented: Bool = false
    @State private var text: String = ""
    @State private var languageDuplicateIsPresented: Bool = false
    @State private var selectAll: Bool = false

    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(entity: Languages.entity(), sortDescriptors: [])

    var languages: FetchedResults<Languages>
    
    var body: some View {
        NavigationView {
            List {
                ForEach(languages) { language in
                    NavigationLink(destination: WordsView(language: language)) {
                        LanguageRowView(language: language, selectAll: selectAll)
                    }
                }
                .onDelete { indexSet in
                    for index in indexSet {
                        viewContext.delete(languages[index])
                    }
                    do {
                        try viewContext.save()
                    } catch {
                        print(error.localizedDescription)
                    }
                }
                .frame(width: screenSize.width - 30, height: 50)
            }
            .navigationBarTitle("Language", displayMode: .inline).opacity(0.8)
            .background(Color.init(.systemGroupedBackground))
            .toolbar {
                ToolbarItemGroup(placement: .navigationBarLeading) {
                    Button(action: {
                        self.text = ""
                        self.addLanguageIsPresented = true
                        print("add Language is presented is: \(self.addLanguageIsPresented)")
                    }, label: {
                        Image("addLanguage")
                            .foregroundColor(.green)
                    })
                    
                    Button(action: {
                        print("selectAll is: \(selectAll) before selection")
                        if selectAll { selectAll = false } else { selectAll = true }
                        print("selectAll is: \(selectAll) after selection")
                    }, label: {
                        if selectAll { Image("unSelectAll") } else { Image("selectAll") }
                    })
                }
                ToolbarItemGroup(placement: .navigationBarTrailing) {
                    Button(action: {
                        
                    }, label: {
                        Image("keyboards")
                            .foregroundColor(.green)
                    })
                    
                    Button(action: {
            
                    }, label: {
                        Image("delete")
                            .foregroundColor(.green)
                    })
                }
            }
        }
        
        addLanguageAlert(title: "Add Language or Sub-Division", isShown: $addLanguageIsPresented, text: $text, onDone: { text in
            print("Inside addLanguageAlert")
            guard text.count > 0 else { return }
            
            // submit language to the addAndSaveLanguage method
            let newLanguage = Languages(context: viewContext)
            newLanguage.name = text
            newLanguage.ckupload = true

            let selectedLanguage = languages.filter { $0.selected == true }
            if selectedLanguage.count > 0 {
                newLanguage.selected = false
            } else {
                newLanguage.selected = true
            }
            newLanguage.setAsHomework = false
            newLanguage.selectedHomework = false
            newLanguage.tickedHomework = false
            newLanguage.tickedLanguage = false

            let recordName = "idlanguage-\(UUID())"
            let zone = CKRecordZone(zoneName: "languagesList")
            let identification = CKRecord.ID(recordName: recordName, zoneID: zone.zoneID)
            let record = CKRecord(recordType: "Languages", recordID: identification)
            let coder = NSKeyedArchiver(requiringSecureCoding: true)
            record.encodeSystemFields(with: coder)
            let metadata = coder.encodedData
            newLanguage.ckmetadata = metadata
            newLanguage.ckrecordname = recordName

            do {
                try viewContext.save()
                print("Language saved.")
            } catch {
                print(error.localizedDescription)
            }
        })
    }
}

struct LanguageView_Previews: PreviewProvider {
    static var previews: some View {
        LanguageView()
    }
}

Tapping on a row loads wordsView():

import CoreData

struct WordsView: View {
        
    @State private var addWordIsPresented: Bool = false
    @State private var english: String = ""
    @State private var foreign: String = ""
    @State private var selectAll: Bool = false

    @Environment(\.managedObjectContext) private var viewContext
    
    let language: Languages
    
    var body: some View {
    
        var words = language.words?.allObjects as! [Words]
    
        NavigationView {
            List {
                ForEach(words) { word in
                    WordRowView(word: word, selectAll: selectAll)
                }
                .onDelete { indexSet in
                    for index in indexSet {
                        viewContext.delete(words[index])
                    }
                    do {
                        try viewContext.save()
                    } catch {
                        print(error.localizedDescription)
                    }
                }
                .frame(width: screenSize.width, height: 75)
            }
            .navigationBarTitle("\(language.name ?? "")", displayMode: .inline).opacity(0.8)
            .background(Color.init(.systemGroupedBackground))
        }
        .toolbar {
            ToolbarItemGroup(placement: .navigationBarLeading) {
                Button(action: {
                    self.english = ""
                    self.foreign = ""
                    self.addWordIsPresented = true
                }, label: {
                    Image("addWord")
                })
                    
                Button(action: {
                    print("selectAll is: \(selectAll) before selection")
                    if selectAll { selectAll = false } else { selectAll = true }
                    print("selectAll is: \(selectAll) after selection")
                }, label: {
                    if selectAll { Image("unSelectAll") } else { Image("selectAll") }
                })
            }
            ToolbarItemGroup(placement: .primaryAction) {
                Button(action: {
        
                }, label: {
                    Image("delete")
                })
            }
        }
        
        addWordAlert(title: "Add Word/Phrase", isShown: $addWordIsPresented, english: $english, foreign: $foreign, onDone: { _,_  in
            // pull out the English and foreign words, or an empty string if there was a problem
            english = english.trimmingCharacters(in: .whitespacesAndNewlines)
            foreign = foreign.trimmingCharacters(in: .whitespacesAndNewlines)
            guard english.count > 0 && foreign.count > 0 else { return }
            
            let newWord = Words(context: viewContext)
            newWord.english = english
            newWord.foreign = foreign
            newWord.language = language
            newWord.ckimage = false
            newWord.ckupload = true
            newWord.bonusCorrectAnswers = 0
            newWord.ckreference = self.language.ckrecordname
            newWord.homeworkAttempts = 0
            newWord.homeworkCorrectAnswers = 0
            newWord.image = nil
            newWord.languageAttempts = 0
            newWord.languageCorrectAnswers = 0
            newWord.tickedWord = false
            newWord.tickedSearchWord = false
            newWord.tickedHomework = false
            newWord.tickedSearchHomework = false
            
            words.append(newWord)
                
            let selectedWord = words.filter{ $0.language == language && $0.selected == true }
            if selectedWord.count > 0 {
                newWord.selected = false
            } else {
                newWord.selected = true
            }
                
            let recordName = "idword-\(UUID())"
            let zone = CKRecordZone(zoneName: "languagesList")
            let id = CKRecord.ID(recordName: recordName, zoneID: zone.zoneID)
            let record = CKRecord(recordType: "Words", recordID: id)
            let coder = NSKeyedArchiver(requiringSecureCoding: true)
            record.encodeSystemFields(with: coder)
            let metadata = coder.encodedData
                
            newWord.ckmetadata = metadata
            newWord.ckrecordname = recordName

            do {
                try viewContext.save()
                print("Word saved.")
            } catch {
                print(error.localizedDescription)
            }
        })
    }
}

struct wordsView_Previews: PreviewProvider {
    static var previews: some View {
        HostingTabBar().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

let screenSize = UIScreen.main.bounds

The 'Test' tab bar item gets moved to a list in the 'More' tab, with a disclosure indicator that loads TestView when tapped. There's also an edit button in the navigation bar that looks like this when tapped:

Edit button in More tab

George
  • 25,988
  • 10
  • 79
  • 133
Tirna
  • 383
  • 1
  • 12

6 Answers6

2

Wrap your view in any view that groups the views inside into one.

ZStack{ //HStack, ScrollView, NavigationStack,etc.
     LanguageView()
}

The addLanguageAlert is out there without a Home. You have to place it somewhere.

lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
0

You have inconsistent use of .tag(_:). The TabView's selection is a Tab binding, whereas your tags are all Ints (0, 1, 2, etc). To fix this, change your tag(_:)s to Tab enum values.

Code:

struct HostingTabBar: View {
    
    private enum Tab: Hashable {
        case language
        case canvas
        case homework
        case test
        case more
    }
    
    @State private var selectedTab: Tab = .language
    
    var body: some View {
        TabView(selection: $selectedTab) {
        LanguageView()
            .tag(Tab.language)
            .tabItem {
                Text("Language")
                Image("language")
            }
        CanvasView()
            .tag(Tab.canvas)
            .tabItem {
                Text("Canvas")
                Image("canvas")
            }
        HomeworkView()
            .tag(Tab.homework)
            .tabItem {
                Text("Homework")
                Image("homework")
            }
        TestView()
            .tag(Tab.test)
            .tabItem {
                Text("Test")
                Image("test")
            }
        MoreView()
            .tag(Tab.more)
            .tabItem {
                Text("More")
                Image("more")
            }
        }
        .accentColor(nil)
    }
}
George
  • 25,988
  • 10
  • 79
  • 133
0

Do you get two LanguageView() tabs if you change the order?

I had this problem. My SwatchListView() contained a ScrollView() and a Spacer(). When I enclosed them both in a VStack, the problem went away. I suspect the Spacer() in my case may have been given the second tab.

Richard Kirk
  • 281
  • 1
  • 12
  • I have this problem, with functionally identical code, have removed all Spacer()s and problem persists. – Edward Hasted Dec 02 '22 at 11:43
  • In my case, the Spacer() was causing it, which surprised me as I was thinking of a Spacer() as a gap between things rather than an active thing in itself. But there may be other things that have the same effect. In my case, enclosing everything in a VStack{} was the fix, rather than removing the Spacer(). – Richard Kirk Dec 02 '22 at 14:01
0

This is because of the Spacer()

In the page that gets duplicated, look for an unnecessary Spacer() and delete it. Typically the Spacer() at fault will be at the top or bottom of the view. Remember to preview as you delete and inspect

DJ bon26

DJ bon26
  • 1
  • 1
0

Well, I've faced the same issue and, based on this answer, the thing you need to do is wrap the NavigationView (of your dupped tab item - LanguageView) within a VStack/HStack and that solves the problem. However, this doesn't make sense to me but really works If anyone knows how to explain why's this happening, please do it.

import CoreData

struct LanguageView: View {
    
    @State private var addLanguageIsPresented: Bool = false
    @State private var text: String = ""
    @State private var languageDuplicateIsPresented: Bool = false
    @State private var selectAll: Bool = false

    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(entity: Languages.entity(), sortDescriptors: [])

    var languages: FetchedResults<Languages>
    
    var body: some View {
        VStack {
            NavigationView {
                List {
                    ForEach(languages) { language in
                        NavigationLink(destination: WordsView(language: language)) {
                            LanguageRowView(language: language, selectAll: selectAll)
                        }
                    }
                    .onDelete { indexSet in
                        for index in indexSet {
                            viewContext.delete(languages[index])
                        }
                        do {
                            try viewContext.save()
                        } catch {
                            print(error.localizedDescription)
                        }
                    }
                    .frame(width: screenSize.width - 30, height: 50)
                }
                .navigationBarTitle("Language", displayMode: .inline).opacity(0.8)
                .background(Color.init(.systemGroupedBackground))
                .toolbar {
                    ToolbarItemGroup(placement: .navigationBarLeading) {
                        Button(action: {
                            self.text = ""
                            self.addLanguageIsPresented = true
                            print("add Language is presented is: \(self.addLanguageIsPresented)")
                        }, label: {
                            Image("addLanguage")
                                .foregroundColor(.green)
                        })
                        
                        Button(action: {
                            print("selectAll is: \(selectAll) before selection")
                            if selectAll { selectAll = false } else { selectAll = true }
                            print("selectAll is: \(selectAll) after selection")
                        }, label: {
                            if selectAll { Image("unSelectAll") } else { Image("selectAll") }
                        })
                    }
                    ToolbarItemGroup(placement: .navigationBarTrailing) {
                        Button(action: {
                            
                        }, label: {
                            Image("keyboards")
                                .foregroundColor(.green)
                        })
                        
                        Button(action: {
                            
                        }, label: {
                            Image("delete")
                                .foregroundColor(.green)
                        })
                    }
                }
            }
        }
        
        addLanguageAlert(title: "Add Language or Sub-Division", isShown: $addLanguageIsPresented, text: $text, onDone: { text in
            print("Inside addLanguageAlert")
            guard text.count > 0 else { return }
            
            // submit language to the addAndSaveLanguage method
            let newLanguage = Languages(context: viewContext)
            newLanguage.name = text
            newLanguage.ckupload = true

            let selectedLanguage = languages.filter { $0.selected == true }
            if selectedLanguage.count > 0 {
                newLanguage.selected = false
            } else {
                newLanguage.selected = true
            }
            newLanguage.setAsHomework = false
            newLanguage.selectedHomework = false
            newLanguage.tickedHomework = false
            newLanguage.tickedLanguage = false

            let recordName = "idlanguage-\(UUID())"
            let zone = CKRecordZone(zoneName: "languagesList")
            let identification = CKRecord.ID(recordName: recordName, zoneID: zone.zoneID)
            let record = CKRecord(recordType: "Languages", recordID: identification)
            let coder = NSKeyedArchiver(requiringSecureCoding: true)
            record.encodeSystemFields(with: coder)
            let metadata = coder.encodedData
            newLanguage.ckmetadata = metadata
            newLanguage.ckrecordname = recordName

            do {
                try viewContext.save()
                print("Language saved.")
            } catch {
                print(error.localizedDescription)
            }
        })
    }
}

struct LanguageView_Previews: PreviewProvider {
    static var previews: some View {
        LanguageView()
    }
}
Takatalvi
  • 670
  • 8
  • 17
0

Real Answer is: Every SwiftUI View should have only one root View inside it's var body: some View {} variable as follows:

var body: some View {
    NavigationStack { /* Other views inside root view */ } <-- NavigationStack as Root View
}

or

var body: some View {
    VStack { /* Other views inside root view */ } <-- VStack as Root View
}

or

var body: some View {
    ScrollView { /* Other views inside root view */ } <-- ScrollView as Root View
}

Do not put multiple root Views inside the body variable which will increase the number of duplicated tabs as much as you increase the root view number. See the following SwiftUI View has two root Views which will create same tab item twice.

Wrong Declarations:

var body: some View {
    NavigationStack { /* Other views view */ } <-- First Root View
    VStack { /* Other views */ } <-- Second Root View
}
// This is wrong bro
Md. Yamin Mollah
  • 1,609
  • 13
  • 26