SwiftUI Elements move down between views - swiftui

I have two views
import SwiftUI
import CoreData
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Image("qr-code")
.resizable()
.scaledToFit()
.position(x: 100, y: 100)
.offset(x: 100)
Text("Thank you")
.position(x: 200)
}.toolbar{
ToolbarItemGroup(placement: .bottomBar) {
NavigationLink(destination: ContentView().navigationBarBackButtonHidden(true)) {
Text("Show QR")
}
Spacer()
NavigationLink(destination: CustomizeView().navigationBarBackButtonHidden(true)) {
Text("Customize")
}
}
}
}
}
}
struct CustomizeView: View {
var body: some View {
List {
Section(header: Text("Important tasks")) {
Text("Task data goes here")
Text("Task data goes here")
}
}.toolbar{
ToolbarItemGroup(placement: .bottomBar) {
NavigationLink(destination: ContentView().navigationBarBackButtonHidden(true)) {
Text("Show QR")
}
Spacer()
NavigationLink(destination: CustomizeView().navigationBarBackButtonHidden(true)) {
Text("Customize")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
CustomizeView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
}
When I click on Customize and the click on Show, I see that the picture moves down. Is it expected behavior? How can I make sure that all elements are in the same positions regardless of how much I clicked on nvaigation buttons?

You should only have one NavigationView in your view hierarchy.
Right now, there are NavigationViews in ContentView and in CustomizeView any time you navigate to either with a NavigationLink, it will add an additional navigation bar to the view, pushing down your content.
To fix this, your root view could just be the NavigationView and then you links could navigation to views that do not contain additional NavigationViews.
struct ContentView: View {
var body: some View {
NavigationView {
BasicView()
}
}
}
struct BasicView : View {
var body: some View {
VStack {
Image(systemName: "pencil")
.resizable()
.scaledToFit()
.position(x: 100, y: 100)
.offset(x: 100)
Text("Thank you")
.position(x: 200)
}.toolbar{
ToolbarItemGroup(placement: .bottomBar) {
NavigationLink(destination: BasicView().navigationBarBackButtonHidden(true)) {
Text("Show QR")
}
Spacer()
NavigationLink(destination: CustomizeView().navigationBarBackButtonHidden(true)) {
Text("Customize")
}
}
}
}
}
struct CustomizeView: View {
var body: some View {
List {
Section(header: Text("Important tasks")) {
Text("Task data goes here")
Text("Task data goes here")
}
}.toolbar{
ToolbarItemGroup(placement: .bottomBar) {
NavigationLink(destination: BasicView().navigationBarBackButtonHidden(true)) {
Text("Show QR")
}
Spacer()
NavigationLink(destination: CustomizeView().navigationBarBackButtonHidden(true)) {
Text("Customize")
}
}
}
}
}

The problem is, I think you have called another ContentView from a ContentView. That's why one more navigation bar is added and shifted
You can create another view called QRShowView and place the qr image there and you have already CustomizeView view . Add two buttons in ContentView like show or customized.
Then call the respective view when is clicked.

Related

How to add the Navigation Bar space in SwiftUI NavigationView

I hide the navigation bar and provide a customized navigation bar.
But my list appears below the navigation bar.
I want the list to appear below the navigation bar when scrolling.
My desired effect:
Actual effect: Blocked 0
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
FirstView()
.tabItem {
Image(systemName: "folder.fill")
Text("Home")
}
SecondView()
.tabItem {
Image(systemName: "folder.fill")
Text("SecondView")
}
}
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct FirstView: View {
var body: some View {
ZStack(alignment: .top) {
List {
ForEach(0..<40, id: \.self) { index in
Text("cell view \(index)")
}
}
.listStyle(.inset)
HStack {
Text("首页")
}
.frame(maxWidth: .infinity)
.frame(height: 44)
.background(.bar)
}
.navigationBarTitleDisplayMode(.inline)
//.ignoresSafeArea(edges: .)
}
}
struct SecondView: View {
var body: some View {
List {
ForEach(0..<40, id: \.self) { index in
Text("FirstView2")
}
}
.listStyle(.inset)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Please do not add padding to the List. This is a security zone problem.
Edit: scrolling under the header
For a glass effect under the header:
You can add a "dummy" element that will push the list one row down. So the the header will cover the dummy element and the whole list will be visible.
struct FirstView: View {
var body: some View {
ZStack(alignment: .top) {
List {
// Dummy Text
Text("")
ForEach(0..<40, id: \.self) { index in
Text("cell view \(index)")
}
}
.listStyle(.inset)
HStack {
Text("首页")
}
.frame(maxWidth: .infinity)
.frame(height: 44)
.background(.bar)
.opacity(0.7)
}
.navigationBarTitleDisplayMode(.inline)
//.ignoresSafeArea(edges: .)
}
}
Original answer
Your FirstView is using a ZStack, which tells exactly the compiler to show one view on top of (covering) the other. Use a VStack, that should solve your issue.
struct FirstView: View {
var body: some View {
VStack {
HStack {
Text("首页")
}
.frame(maxWidth: .infinity)
.frame(height: 44)
.background(.bar)
List {
ForEach(0..<40, id: \.self) { index in
Text("cell view \(index)")
}
}
.listStyle(.inset)
}
.navigationBarTitleDisplayMode(.inline)
//.ignoresSafeArea(edges: .)
}
}

Closing A View From Another View

I am still learning SwiftUI and I have come across a small problem.
In my app, I have a main view. On the top is a search bar and at the bottom, a menu with different buttons. I want to change views when clicking those buttons. However, I only want to change the middle section.
No big deal, I will just put the middle part into a NavigationView. That works alright and I am able to change my views. My problem is that the buttons below do not have any impact on the new view.
To try to simplify: Let’s say I’m on home page. I then click the grocery list button (guess what I’m making school projects lol). My navigation link works just fine and goes to the list. So, now I’m on view 2 let’s say. When I press the home button, it doesn’t close that view and go to my main one. Here is my code setup:
import SwiftUI
struct ContentView: View {
#State private var searchText: String = ""
#State private var action: Int? = 0
var body: some View {
ZStack {
// Top Menu
VStack{
HStack {
Spacer()
TextField("Search",
text: $searchText)
.background(Color.white)
Button(action: {
self.action = 1
}, label: {
Image(systemName: "magnifyingglass.circle")
.font(.largeTitle)
})
Spacer()
}
// Body
NavigationView {
VStack {
Text("Can I See Something")
NavigationLink(destination: SearchView(), tag: 1, selection: $action) {
}
Text("Yes/No")
}
}
Spacer()
// Bottom Menu
HStack (alignment: .top) {
Spacer()
VStack {
Button(action: {
}, label: {
Image(systemName: "house.fill")
.font(.largeTitle)
})
.padding(.top)
Text("Home")
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "newspaper")
.font(.largeTitle)
})
.padding(.top)
Text("Weekly\nAd")
.multilineTextAlignment(.center)
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "checklist")
.font(.largeTitle)
})
.padding(.top)
Text("Grocery\nList")
.multilineTextAlignment(.center)
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "person.crop.circle")
.font(.largeTitle)
})
.padding(.top)
Text("Account")
}
Spacer()
}
}
}
}
}
struct SearchView: View {
var body: some View {
ZStack {
Text("Nothing to see here!")
}
.navigationBarBackButtonHidden(true)
}
}
SearchView is a separate view (in its own file) in the app that opens up when the magnifying glass button is pressed. Currently it does not do anything. However I want to be able to press those buttons on this view above to still navigate the app.
Also, on another note, is there anyway to get rid of the back button?
In your code the buttons do not have any function.
Instead of creating a tab bar on your own, I'd rather take something like:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
MainView()
.tabItem {
Label("Home", systemImage: "house.fill")
}
NewsView()
.tabItem {
Label("Weekly\nAd", systemImage: "newspaper")
}
OrderView()
.tabItem {
Label("Grocery\nList", systemImage: "checklist")
}
AccountView()
.tabItem {
Label("Account", systemImage: "person.crop.circle")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MainView: View {
var body: some View {
Text("Home View")
}
}
struct NewsView: View {
var body: some View {
Text("News View")
}
}
struct OrderView: View {
var body: some View {
Text("Order View")
}
}
struct AccountView: View {
var body: some View {
Text("Account View")
}
}
In that case you'll have to create a view for each tab you are configuring (see the last 4 structs).
If you want to do it with a Stack with your own created buttons, I think you should create al 4 views as well and then you either hide them or put them out of focus by using an offset. In that case the buttons should hide/show the specific views or change the offset accordingly to move the specific views into the visible area. With the offset you also can add some animation.
Regarding the search bar on top of your app, since the views are all different, I wouldn't keep the same search bar everywhere, but if you really want to have it that way, you can embed the code + your search bar into a VStack (as you did it in your example).

NavigationLink popping up and then disappearing

Does anyone know why when I have two NavigationLinks in the same view, there is this bug that my view pops up correctly, but then immediately disappears. I've tried different solutions, but none of them seem to be working. Any help would be appreciated. I believe the error might be related to isActive, but I'm not sure.
So, basically, I have a button that is supposed to open up the MessageView through the NavigationLink and dismiss the ChatView and ConversationCell views.
import SwiftUI
struct ConversationsView: View {
#State var isShowingNewMessageView: Bool = false
#State var showChat: Bool = false
var body: some View {
ZStack(alignment: .bottomTrailing) {
NavigationLink(isActive: $showChat, destination: {ChatView()}) {
// EmptyView()
}
NavigationLink(destination: EmptyView(), label: {})
.navigationViewStyle(StackNavigationViewStyle())
ScrollView {
VStack {
ForEach(0..<20) { _ in
NavigationLink {
ChatView()
} label: {
ConversationCell()
}
}
}
.padding()
}
Button {
self.isShowingNewMessageView.toggle()
} label: {
Image(systemName: "envelope")
.resizable()
.scaledToFit()
.frame(width: 32, height: 32)
.padding()
}
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Circle())
.padding()
.sheet(isPresented: $isShowingNewMessageView, content: {
NewMessageView(startChat: $showChat, show: $isShowingNewMessageView)
})
}
}
}
struct ConversationsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ConversationsView()
}
}
}

SwiftUI 2.0: Close button is not dismissing the view - how do I get the Close button to return to the previous view?

I have tried to use Buttons and Navigation Links from various examples when researched on this channel and on the net. The NavigationLink would be ok, except that the NavigationView is pushing everything down in my view.
I have a view that contains an image and a text like this: ( x Close) but when I use the code below, the Close button is not doing anything.
In ContentView() I have a (?) button that takes me from WalkthroughView(), then to the PageTabView, then to this view, TabDetailsView:
ContentView():
ZStack {
NavigationView {
VStack {
Text("Hello World")
.padding()
.font(.title)
.background(Color.red)
.foregroundColor(.white)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
showOnBoarding = true
}
} label: {
Image(systemName: "questionmark.circle.fill")
}
}
}
}
.accentColor(.red)
.disabled(showOnBoarding)
.blur(radius: showOnBoarding ? 3.0 : 0)
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
}
.onAppear {
if !isWalkthroughViewShowing {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
showOnBoarding.toggle()
isWalkthroughViewShowing = true
}
}
}
}
WalkthroughView():
var body: some View {
ZStack {
GradientView()
VStack {
PageTabView(selection: $selection)
// shows Previous/Next buttons only
ButtonsView(selection: $selection)
}
}
.transition(.move(edge: .bottom))
}
PageTabView():
var body: some View {
TabView(selection: $selection) {
ForEach(tabs.indices, id: \.self) { index in
TabDetailsView(index: index)
}
}
.tabViewStyle(PageTabViewStyle())
}
below, is the TabDetailsView():
At the top of the view is this Close button, when pressed, should send me back to ContentView, but nothing is happening.
struct TabDetailsView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let index: Int
then, inside the body:
VStack(alignment: .leading) {
Spacer()
VStack(alignment: .leading) {
// Button to close each walkthrough page...
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}
.padding(.leading)
.font(.title2)
.accentColor(.orange)
Spacer()
VStack {
Spacer()
Image(tabs[index].image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 415)
.padding(.leading, 10)
Text(tabs[index].title)
.font(.title)
.bold()
Text(tabs[index].text)
.padding()
Spacer()
}
.foregroundColor(.white)
}
}
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
Inserting view like above is not a presentation in standard meaning, that's why provided code does not work.
As this view is shown via showOnBoarding it should be hidden also via showOnBoarding, thus the solution is to pass binding to this state into view where it will be toggled back.
Due to deep hierarchy the most appropriate way is to use custom environment value. For simplicity let's use ResetDefault from https://stackoverflow.com/a/61847419/12299030 (you can rename it in your code)
So required modifications:
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
.environment(\.resetDefault, $showOnBoarding)
}
and in child view
struct TabDetailsView: View {
#Environment(\.resetDefault) var showOnBoarding
// .. other code
Button(action: {
self.showOnBoarding.wrappedValue.toggle()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}

SwiftUI Popover Size is not expanding to fit content

Here is my code
struct ContentView: View {
#State var showingPopover = false
var body: some View {
VStack {
Spacer()
Text("Hello World")
Spacer()
HStack {
Spacer()
Button {
self.showingPopover.toggle()
} label: {
Image(systemName: "plus.circle")
}
.popover(isPresented: $showingPopover) {
List(0..<100) { Text("\($0)") }
}.padding(30)
}
}
}
}
This should produce a really nice popover coming from the plus button. But all I get is a really squashed down popover.
Any idea what I am missing here? Is there a way to tell the popover to expand more (without specifying a size)?
You may use a ScrollView and ForEach instead of a List:
struct ContentView: View {
#State var showingPopover = false
var body: some View {
VStack {
Spacer()
Text("Hello World")
Spacer()
HStack {
Spacer()
Button(action: {
self.showingPopover.toggle()
}) {
Image(systemName: "plus.circle")
}
.padding(30)
}
}
// can be attached to the button as well (as in the question)
.popover(isPresented: $showingPopover,
attachmentAnchor: .point(.bottomTrailing),
arrowEdge: .bottom) {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0 ..< 100) {
Text("\($0)")
}
}
}
}
}
You can provide a custom frame for the List. Also, don't forget to embed List inside a ScrollView if you want it to scroll.
ScrollView {
List(0..<100) {
Text("\($0)")
}
.frame(width: 100, height: 250)
}