Swiftui how to add a view above a tabview? - ios

The requirement is to display a banner above the tabbar. So that the banner does not disappear when the tabs are changed? How can I achieve it?

I can think of starting points, to help you see ways to approach this, but I don't think either idea really meets your requirements. It will depend on the details of whether the banner is permanent, where its content comes from, etc.
The first idea:
struct BannerView : View {
var text : String
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Text(text)
Spacer()
}.background(Color.orange)
}
}
}
Then you can include this in a ZStack along with your tabs:
TabView {
ZStack {
Text("Tab 1")
BannerView("BANNER")
}.tabItem { Text("Home") }
ZStack {
Text("Tab 2")
BannerView("BANNER")
}.tabItem { Text("History") }
}
The second idea uses the BannerView from the first idea, but in a slightly cleaner way, still not great:
struct TabWrapperWithOptionalBanner<Content> : View where Content : View {
var showBanner : Bool
var content : Content
init(showBanner : Bool, #ViewBuilder content: () -> Content) {
self.showBanner = showBanner
self.content = content()
}
var body: some View {
ZStack {
content
if showBanner {
BannerView(text: "BANNER")
}
}
}
}
then your ContentView looks like this:
TabView {
TabWrapperWithOptionalBanner(showBanner: showBanner) {
Text("Tab 1")
}.tabItem { Text("Home") }
TabWrapperWithOptionalBanner(showBanner: showBanner) {
Text("Tab 2")
}.tabItem { Text("History") }
}

Update Try a pair of TabViews bound to the same State:
struct BanneredTabView: View {
#State private var selected = panels.one
var body: some View {
VStack {
TabView(selection: $selected) {
panels.one.label.tag(panels.one)
panels.two.label.tag(panels.two)
panels.three.label.tag(panels.three)
}
.tabViewStyle(PageTabViewStyle())
Text("Banner")
.frame(height: 40, alignment: .top)
TabView(selection: $selected) {
ForEach(panels.allCases) { panel in
Text("").tabItem {
panel.label
}
.tag(panel)
}
}
.frame(height: 30)
}
}
enum panels : Int, CaseIterable, Identifiable {
case one = 1
case two = 2
case three = 3
var label : some View {
switch self {
case .one:
return Label("Tab One", systemImage: "1.circle")
case .two:
return Label("Tab Two", systemImage: "2.square")
case .three:
return Label("Tab Three", systemImage: "asterisk.circle")
}
}
// so the enum can be identified when enumerated
var id : Int { self.rawValue }
}
}

Related

Animation not working inside sheet for swiftui

I am using a sheet to present a list of options and on click of the option I want to change the view with the animation of sliding from trailing. As per my understanding and what I have read on various sites I have written this code but I am not sure why it is not working the way intended. I just want to know where exactly this code went wrong.
struct XYZ: App {
let persistenceController = PersistenceController.shared
#State var isPresented : Bool = false
#State var isSwiped : Bool = false
var body: some Scene {
WindowGroup {
optionList(isPresented: $isPresented)
.sheet(isPresented: $isPresented, content: {
Text("This is from modal view!")
.onTapGesture {
withAnimation(Animation.easeIn(duration: 10)){
isSwiped.toggle()
}
}
if isSwiped {
checkedList()
.transition(.move(edge: .trailing))
}
})
}
}
}
struct optionList : View {
#Binding var isPresented : Bool
var body: some View {
Text("This is a testView")
.onTapGesture {
withAnimation{
isPresented.toggle()
}
}
}
}
struct checkedList : View {
#State var name : String = "WatchList"
var arr = ["First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Seventh"]
#State var mp : [Int:Int] = [:]
var body: some View {
VStack{
HStack{
TextField("WatchlistName", text: $name)
.padding(.all)
Image(systemName: "trash.fill")
.padding(.all)
.onTapGesture {
print("Delete watchList!!")
}
}
ScrollView{
ForEach(arr.indices) { item in
HStack (spacing: 0) {
Image(systemName: mp.keys.contains(item) ? "checkmark.square" : "square")
.padding(.horizontal)
Text(arr[item])
}
.padding(.bottom)
.frame(width: UIScreen.main.bounds.width, alignment: .leading)
.onTapGesture {
if mp.keys.contains(item) {
mp[item] = nil
} else {
mp[item] = 1
}
}
}
}
Button {
print("Remove Ticked Elements!")
deleteWatchListItem(arr: Array(mp.keys))
} label: {
Text("Save")
}
}
}
func deleteWatchList(ind: Int){
print(ind)
}
func deleteWatchListItem(arr : [Int]) {
print(arr)
}
}
I tried to create a view and with the animation using withanimation with a bool variable tried to change the view.
It sounds like what you want is to push the checkedList on to a NavigationStackā€¦
struct ContentView: View {
#State var isPresented : Bool = false
var body: some View {
Text("This is a testView")
.onTapGesture {
isPresented.toggle()
}
.sheet(isPresented: $isPresented, content: {
NavigationStack {
NavigationLink("This is from modal view!") {
checkedList()
}
}
})
}
}

How to make ScrollViewReader scroll to top of List?

I have List within a TabView and allowing the user to scroll to the top when they double tap a tab. I'm using a ScrollViewReader to scroll to a specific anchor. However, it is not fully scrolling to the top of the list because of the navigation title, see the title overlapping the content:
I'm using the technique from this blog post for more context. Below is a working sample:
struct ContentView: View {
#State private var _selectedTab: SelectedTab = .one
#State private var tabbedTwice = false
var selectedTab: Binding<SelectedTab> {
Binding(
get: { _selectedTab },
set: {
if $0 == _selectedTab {
tabbedTwice = true
}
_selectedTab = $0
}
)
}
enum SelectedTab: String {
case one
case two
}
var body: some View {
ScrollViewReader { proxy in
TabView(selection: selectedTab) {
NavigationView {
List {
Section {
ForEach(1...50, id: \.self) { index in
Text("Item \(index.formatted())")
}
}
.id(SelectedTab.one.rawValue)
Section {
Text("Section 2")
}
}
.navigationTitle("First")
}
.tabItem {
Label("One", systemImage: "clock.arrow.circlepath")
}
.tag(SelectedTab.one)
NavigationView {
List {
Section(header: Text("Header").id(SelectedTab.two.rawValue)) {
ForEach(50...100, id: \.self) { index in
Text("Item \(index.formatted())")
}
}
Section {
Text("Section 2")
}
}
.navigationTitle("Second")
}
.tabItem {
Label("Two", systemImage: "list.bullet")
}
.tag(SelectedTab.two)
}
.onChange(of: tabbedTwice) {
guard $0 else { return }
withAnimation { proxy.scrollTo(_selectedTab.rawValue, anchor: .top) }
tabbedTwice = false
}
}
}
}
Is there a better place to put the anchor identifier? I tried putting on the first section and also the section header which worked better but still not scrolling to the very top. How can this be achieved?

Creating two sidebars in iPadOS application using SwiftUI

I have created one sidebar with NavigationView, which by default appends to the left of the landscape view of application. However I wanted to have another on the right side.
NavigationView {
List {
Label("Pencil", systemImage: "pencil")
Label("Paint", systemImage: "paintbrush.fill")
Label("Erase", systemImage: "quote.opening")
Label("Cutter", systemImage: "scissors")
Label("Eyedropper", systemImage: "eyedropper.halffull")
Label("Draw Line", systemImage: "line.diagonal")
}
.listStyle(SidebarListStyle())
}
Here's a simple working example made with SwiftUI:
struct ContentView: View {
var body: some View {
NavigationView{
TagView()
Text("Default second View")
Text("Default Third View")
}
}
}
struct TagView: View {
let tags = ["Apple", "Google"]
var body: some View {
List{
ForEach(tags, id: \.self) { name in
NavigationLink {
ProductView(tag: name)
} label: {
Text(name)
}
}
}
}
}
struct ProductView: View {
var tag: String
var products: [String] {
if tag == "Apple" {
return ["iPhone", "iPad", "MacBook"]
} else {
return ["resuable stuff"]
}
}
var body: some View {
List{
ForEach(products, id: \.self) { name in
NavigationLink {
DetailsView()
} label: {
Text(name)
}
}
}
}
}
struct DetailsView: View {
var body: some View {
Text("Detailed explanation about product")
}
}

IOS App Crash when show keyboard after change binding inside sheet

this is my code with swiftui, when I change tradingMode inside sheet by click button, after show keyboard from ContentView => app crash. Pease help me and thanks!
enum TradingMode {
case Derivatives, Equities
}
struct ContentView: View {
#State var tradingMode : TradingMode = TradingMode.Equities
#State var isShowSecondView = false
var body: some View {
VStack(content: {
Button("show second view") {
isShowSecondView.toggle()
}
TabView {
switch tradingMode {
case .Equities:
VStack(content: {
Text("Tab 1 Un")
.padding()
TextField("ple", text: .constant(""))
})
.tabItem {
Text("tab 1")
}.tag(0)
case .Derivatives:
VStack(content: {
Text("Tab 1 Der")
.padding()
TextField("ple", text: .constant(""))
})
.tabItem {
Text("tab 1")
}.tag(0)
}
switch tradingMode {
case .Equities:
VStack(content: {
Text("Tab 2 Un")
.padding()
TextField("ple", text: .constant(""))
})
.tabItem {
Text("tab 2")
}.tag(1)
case .Derivatives:
VStack(content: {
Text("Tab 2 Der")
.padding()
TextField("ple", text: .constant(""))
})
.tabItem {
Text("tab 2")
}.tag(1)
}
}
})
.sheet(isPresented: $isShowSecondView, content: {
SecondView(tradingMode: $tradingMode)
})
}
}
struct SecondView: View {
#Environment(\.presentationMode) var presentation
#Binding var tradingMode : TradingMode
var body: some View {
VStack(content: {
Button("Change state") {
if tradingMode == .Derivatives {
tradingMode = .Equities
} else {
tradingMode = .Derivatives
}
self.presentation.wrappedValue.dismiss()
}
})
}
}
This is my bug: https://drive.google.com/file/d/18eXCmlByGqJylE_hCZbtJ4Rn0wmI0bb_/view?usp=sharing
Your code work well.
You can try to clean buildĀ folder CMD + shift + K or clear derived data in ~/Library/Developer/Xcode/DerivedData

Is there a problem with the way I'm structuring my SwiftUI project that causes .navigationTitle to not appear?

My SwiftUI project refuses to display the navigation title after a certain point. I'm using a navigation structure that I haven't seen implemented on any example projects I've seen, but it makes sense to me and has been seeming to work thus far. My suspicion is that since I'm using a different navigation structure that it is part of the problem. The .navigationBar is there on every page, but the title doesn't display.
SettingsView.swift screen
I've tried many solutions on Stack Overflow and otherwise. I've tried every combination of .navigationBarHidden(false), .navigationBarTitle() and .navigationBarBackButtonHidden(true) on every page listed below, with no change. I've also tried every location I could think of to place these combinations of .navigationBar modifiers.
Recently, I discovered .toolbar, and this changes nothing either. My suspicion is that (as seen in the code snippets below) since the NavigationView is in the first view (WelcomeUI.swift), I can't place the .navigationBarTitle deeper in the code.
Below is my current navigation structure, and bits of code from each file:
WelcomeUI.swift
struct WelcomeUI: View {
var body: some View {
NavigationView {
VStack {
//NavigationLink(destination: SignupUI(), label: {
//Text("Sign Up")
//}
NavigationLink(destination: LoginUI(), label: {
Text("Log In")
}
}
}
}
}
LoginUI.swift
struct LoginUI: View {
var body: some View {
VStack {
NavigationLink(destination: MainUI(), label: { Text("Log In") })
//Button(action: { ... }
}
.navigationBarHidden(false)
}
}
Note: SignupUI.swift is essentially the same as LoginUI.swift
MainUI.swift
struct MainUI: View {
var body: some View {
TabView {
//SpendingView()
//.tabItem {
//Image(...)
//Text("Spending")
//}
//SavingView()
//.tabItem {
//Image(...)
//Text("Saving")
//}
//AddView()
//.tabItem {
//Image(...)
//Text("Add")
//}
//EditView()
//.tabItem {
//Image(...)
//Text("Edit")
//}
SettingsView()
.tabItem {
//Image(...)
Text("Settings")
}
}
.navigationBarBackButtonHidden(true)
}
}
Note: All views in MainUI.swift are structured the same.
SettingsView.swift
struct SettingsView: View {
var body: some View {
ZStack {
Form {
Section(header: Text("Section Header")) {
NavigationLink(destination: WelcomeUI()) {
Text("Setting Option")
}
}
Section {
//Button("Log Out") {
//self.logout()
//}
Text("Log Out")
}
}
.navigationBarTitle("Settings") // This has no effect on code no matter where it is place in SettingsView.swift
}
}
}
I should also note that only the pages after MainUI.swift is affected by this. WelcomeUI.swift and LoginUI.swift work as expected.
Look at the MainUI navigationTitle. I just put a title in every View to find what was going on.
import SwiftUI
struct SubSpendingView: View {
var body: some View {
ScrollView{
Text("SubSpendingView")
}.navigationBarTitle("SubSpending"
//, displayMode: .inline
)
}
}
struct SpendingView: View {
var body: some View {
ScrollView{
Text("SpendingView")
NavigationLink("subSpending", destination: SubSpendingView())
}.padding()
}
}
struct WelcomeUI: View {
var body: some View {
NavigationView {
VStack {
//NavigationLink(destination: SignupUI(), label: {
//Text("Sign Up")
//}
NavigationLink(destination: LoginUI(), label: {
Text("Go to Log In")
})
}.navigationTitle(Text("WelcomeUI"))
}
}
}
struct SettingsView: View {
var body: some View {
VStack{
ZStack {
Form {
Section(header: Text("Section Header")) {
NavigationLink(destination: WelcomeUI()) {
Text("Setting Option")
}
}
Section {
//Button("Log Out") {
//self.logout()
//}
Text("Log Out")
}
}
Button("say-high", action: {print("Hi")})
// This has no effect on code no matter where it is place in SettingsView.swift
}
}//.navigationBarTitle("Settings")
}
}
struct LoginUI: View {
var body: some View {
VStack {
NavigationLink(destination: MainUI(), label: { Text("Log In") })
//Button(action: { ... }
}.navigationTitle(Text("LoginUI"))
.navigationBarHidden(false)
}
}
struct MainUI: View {
#State var selectedTab: Views = .adding
var body: some View {
TabView(selection: $selectedTab) {
SpendingView()
.tabItem {
Image(systemName: "bag.circle")
Text("Spending")
}.tag(Views.spending)
//SavingView()
//.tabItem {
//Image(...)
//Text("Saving")
//}
Text("Adding View")
.tabItem {
Image(systemName: "plus")
Text("Add")
}.tag(Views.adding)
Text("Edit View")
.tabItem {
Image(systemName: "pencil")
Text("Edit")
}.tag(Views.edit)
SettingsView()
.tabItem {
Image(systemName: "gear")
Text("Settings")
}.tag(Views.settings)
}
//This overrides the navigationTitle in the tabs
.navigationBarTitle(Text(selectedTab.rawValue)
//, displayMode: .inline
)
.navigationBarBackButtonHidden(true)
}
}
enum Views: String{
case settings = "Settings"
case spending = "Spending"
case adding = "Add"
case edit = "Edit"
}
struct PaddyNav: View {
var body: some View {
WelcomeUI()
//Wont work
.navigationTitle(Text("PaddyNav"))
}
}
struct PaddyNav_Previews: PreviewProvider {
static var previews: some View {
PaddyNav()
}
}

Resources