I embedded a TabView in a NavigationView and the Text in the view gets pushed down slightly. I've tried moving the views around however it just ends up breaking functionally. You can see the green text is not vertically aligned to the rest of the device, but instead aligned to the content under the navigation bar.
var body: some View {
NavigationView{
TabView {
Text("TEST 1").foregroundColor(color).font(Font.custom("Catamaran-ExtraBold", size: 48)).navigationTitle("TEST 1")
Text("TEST 2").foregroundColor(color).font(Font.custom("Catamaran-ExtraBold", size: 48)).navigationTitle("TEST 2")
}
.foregroundColor(.black).navigationBarTitleDisplayMode(.inline)
.font(Font.custom("Catamaran-ExtraBold", size: 20))
.navigationBarItems(
leading:
NavigationLink(destination: SettingsView(), label: {
Image(systemName: "gearshape")
})).foregroundColor(colorScheme == .dark ? .white : .black)
}
.navigationViewStyle(.stack)
.ignoresSafeArea()
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))
}
It's a bit strange what you are trying to achieve. Centering it as if the NavigationView wasn't there means that the view is no longer centered in its container. It will look strange and not centered like this.
However, here is how you can achieve this. This example uses GeometryReaders to measure the height of the navigation bar, and take away half that so the text now appears centered to the safe area.
Before:
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
Text("TEST 1")
.font(.title.bold())
.foregroundColor(.green)
.navigationTitle("TEST 1")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red.opacity(0.1))
Text("TEST 2")
.font(.title.bold())
.foregroundColor(.green)
.navigationTitle("TEST 2")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red.opacity(0.1))
}
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))
}
.navigationViewStyle(.stack)
}
}
After:
struct ContentView: View {
var body: some View {
GeometryReader { geoRoot in // <- HERE
NavigationView {
GeometryReader { geo in // <- HERE
let yOffset = (geoRoot.safeAreaInsets.top - geo.safeAreaInsets.top) / 2 // <- HERE
TabView {
Text("TEST 1")
.font(.title.bold())
.foregroundColor(.green)
.navigationTitle("TEST 1")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red.opacity(0.1))
// <- Also replicate offset here, not done for demo
Text("TEST 2")
.font(.title.bold())
.foregroundColor(.green)
.navigationTitle("TEST 2")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red.opacity(0.1))
.offset(y: yOffset) // <- HERE
}
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))
}
}
.navigationViewStyle(.stack)
}
}
}
Result
Before: TEST 1 tab
After: TEST 2 tab
Related
I'm looking how to align text to the top of screen, do you know how to?
import SwiftUI
struct RegisterSignInScreen: View {
var body: some View {
VStack {
Text("Welcome Back!")
.font(.title)
.fontWeight(.bold)
.multilineTextAlignment(.center)
.padding(.bottom, 5.0)
Text("Please sign in to your account")
.font(.subheadline)
.fontWeight(.bold)
.foregroundColor(Color.gray)
.multilineTextAlignment(.center)
}
}
}
struct RegisterSignInScreen_Previews: PreviewProvider {
static var previews: some View {
RegisterSignInScreen()
}
}
image
I tried to embed my VStack in a ZStack:
ZStack(alignment: .top)
But it didn't work.
To see why this is happening you can add a background to your VStack or ZStack
ZStack {
VStack {
//Text items
}
}
.background(.red)
What you will notice (maybe unexpectedly) is that the stack is only as big as it needs to be to contain the text items.
When you add alignment to the stack it aligns the content within the stack to the specified edge of the relatively small stack. It doesn't align the stack to an edge of the screen, or change the size of the stack. What you're actually looking to do is align the stack, not it's content.
In order to align to the top of the screen you can make the stack fill the entire height of the screen. Either by adding a Spacer() at the bottom of the VStack that will fill the remaining vertical space pushing the content upwards, or by applying a frame with .infinity maxHeight: and top aligned content to the VStack.
VStack {
Text("Hello World!")
Spacer()
}
VStack {
Text("Hello World!")
}
.frame(maxHeight: .infinity, alignment: .top)
Alternatively if required for your view, you can do a similar thing with a second stack containing your text stack like so:
var body: some View {
VStack {
welcomeText
Spacer()
}
}
var welcomeText: some View {
VStack {
Text("Welcome Back!")
.font(.title)
.fontWeight(.bold)
.multilineTextAlignment(.center)
.padding(.bottom, 5.0)
Text("Please sign in to your account")
.font(.subheadline)
.fontWeight(.bold)
.foregroundColor(Color.gray)
.multilineTextAlignment(.center)
}
}
You can do this in a couple of ways, but the most obvious way is to simply add a Spacer() in the bottom of the VStack
Like so:
struct RegisterSignInScreen: View {
var body: some View {
VStack {
Text("Welcome Back!")
Text("Please sign in to your account")
Spacer() // <- HERE
}
}
}
This will push your content in the VStack to the top.
Alternatively, you can add a frame modifier and force your VStack height and add the alignment there using .frame
struct RegisterSignInScreen: View {
var body: some View {
VStack {
Text("Welcome Back!")
Text("Please sign in to your account")
}
.frame(maxHeight: .infinity, alignment: .top) // <- HERE
}
}
ZStack(alignment: .top) works, but not the same way as you think)
try to do:
ZStack(alignment: .top){
}
.background(Color.green)
and you will understand why so :)
you need to use spacer :)
VStack {
YourView()
Spacer()
}
I just started with SwiftUI and wanted to build a simple home app. In order to have a better overview I structured my site using a collapsible view, which when expanded reveals the home view. However, I've been struggling to make a dedicated "plus" button that adds another one of the predefined collapsable views to my Content View
Here is the code I made (with the help of a tutorial) for the CollapsableViewModel:
var body: some View {
ZStack{
VStack {
HStack{
Button(
action: { self.collapsed.toggle() },
label: {
HStack {
TextField("Empty...", text: $textFieldText)
.font(.title2.bold())
Spacer()
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
}
.padding(1)
}
VStack {
self.content()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)
.clipped()
.transition(.slide)
}
}
}
And here's my Content View:
var body: some View {
ScrollView{
HStack{
CollapsableViewModel(
label: { Text("") .font(.title2.bold()) },
content: {
HStack {
Text("turn off light.")
Text("turn on ac.")
Spacer()}
.frame(maxWidth: .infinity)
}
)
.frame(maxWidth: .infinity)
}
.padding()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.statusBar(hidden: false)
.navigationBarHidden(true)
}
Now, the problem is that I really don't know where to start with this. My guess is that I have to work with an button that triggers a "showCollapsView" action. But I don't how I make it so that it appears under the VStack of my main ContentView. Any tipps would be appreciated, as I would love to understand this whole process myself!
I have following problem. I want to create a vertical ScrollView with many rows. At the bottom of the view I have an info bar which appears over the scroll view because I put all the items in a ZStack. Here is my code and what it produces:
struct ProblemView: View {
var body: some View {
ZStack {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ForEach(0..<100, id:\.self) {i in
HStack {
Text("Text \(i)")
.foregroundColor(.red)
Spacer()
Image(systemName: "plus")
.foregroundColor(.blue)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Divider()
}
}
}
VStack {
Spacer()
HStack {
Text("Some Info here")
Image(systemName: "info.circle")
.foregroundColor(.blue)
}
.padding()
.frame(maxWidth: .infinity)
.ignoresSafeArea()
.background(.ultraThinMaterial)
}
}
}
}
struct ProblemView_Previews: PreviewProvider {
static var previews: some View {
ProblemView()
}
}
As you can see the drag indicator is hidden behind the info frame. Also the last item can't be seen because it is also behind the other frame. What
I want is that the drag indicator stops at this info frame. Why am I using a ZStack and not just a VStack? I want that this opacity effect behind the info frame, you get when you scroll.
A edit on my preview post has been added and therefore I cannot edit it... I am just gonna post the answer as an other one then.
This is the code that fixes your problem:
import SwiftUI
struct ProblemView: View {
var body: some View {
ScrollView {
VStack {
ForEach(0..<100, id:\.self) {i in
HStack {
Text("Text \(i)")
.foregroundColor(.red)
Spacer()
Image(systemName: "plus")
.foregroundColor(.blue)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Divider()
}
}
.frame(maxWidth: .infinity)
}
.safeAreaInset(edge: .bottom) { // 👈🏻
VStack {
HStack {
Text("Some Info here")
Image(systemName: "info.circle")
.foregroundColor(.blue)
}
.padding()
.frame(maxWidth: .infinity)
.ignoresSafeArea()
.background(.ultraThinMaterial)
}
}
}
}
struct ProblemView_Previews: PreviewProvider {
static var previews: some View {
ProblemView()
}
}
We cannot control offset of indicator, but we can make all needed views visible by injecting last empty view with the same height (calculated dynamically) as info panel.
Here is possible approach. Tested with Xcode 13.2 / iOS 15.2
struct ProblemView: View {
#State private var viewHeight = CGFloat.zero
var body: some View {
ZStack {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ForEach(0..<100, id:\.self) {i in
HStack {
Text("Text \(i)")
.foregroundColor(.red)
Spacer()
Image(systemName: "plus")
.foregroundColor(.blue)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Divider()
}
Color.clear
.frame(minHeight: viewHeight) // << here !!
}
}
VStack {
Spacer()
HStack {
Text("Some Info here")
Image(systemName: "info.circle")
.foregroundColor(.blue)
}
.padding()
.frame(maxWidth: .infinity)
.ignoresSafeArea()
.background(.ultraThinMaterial)
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
}
}
.onPreferenceChange(ViewHeightKey.self) {
self.viewHeight = $0
}
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = value + nextValue()
}
}
import SwiftUI
struct Test: View {
var body: some View {
VStack{
Text("dklf")
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
}
Why is the VStack not aligned in the middle of the Screen vertically?
UIScreen.main.bounds does not account for the Safe Area, so this is what you probably wanted:
var body: some View {
VStack{
Text("dklf")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
Ok thanks. I don't understand, why this blue border isn't at the top. If I add .statusBar(hidden: true) after the VStack, the blue border stays also there.
struct Test: View {
var body: some View {
VStack{
Color.yellow
}
.ignoresSafeArea()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
I Create List and add VStack inside and added some views inside VStack. When I run the project, I observer scrolling of List going beyond the safe area. FYI if I remove Frame property still same result.Simulator gif
struct ContentView : View {
var body: some View {
List(0..<5) { item in
HStack(alignment: VerticalAlignment.top, spacing: 5) {
Image(systemName: "photo")
VStack(alignment: HorizontalAlignment.leading, spacing: 10) {
Text("USA")
.font(.headline)
Text("This is an extremely long string that will never fit even the widest of Phones Excerpt From: Paul Hudson. “SwiftUI by Example”. Apple Books. ")
.lineLimit(nil)
.font(.subheadline)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.red)
.onAppear() {
print("on Appear")
}.onDisappear() {
print("on Disappear")
}
}
}
Inspired by Shauket Sheikh. You can directly add the .padding(.top) to the List and it's done. No need for a VStack.
struct ContentView : View {
var body: some View {
List(0..<5) { item in
HStack(alignment: VerticalAlignment.top, spacing: 5) {
Image(systemName: "photo")
VStack(alignment: HorizontalAlignment.leading, spacing: 10) {
Text("USA")
.font(.headline)
Text("This is an extremely long string that will never fit even the widest of Phones Excerpt From: Paul Hudson. “SwiftUI by Example”. Apple Books. ")
.lineLimit(nil)
.font(.subheadline)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.red)
.onAppear() {
print("on Appear")
}.onDisappear() {
print("on Disappear")
}
.padding(.top)
}
}
I had the same problem with my ScrollView
My solution was simpler than the rest, so give this a shot:
Just add .clipped() modifier to your List or ScrollView and this should prevent your content from scrolling out of its bounds.
And you can then combine this with edgesIgnoringSafeArea(.bottom) if you want your content to still scroll off screen from the bottom. But watch out - edgesIgnoringSafeArea(.bottom) has to come after .clipped() if you want this effect.
You can also use VStack and set .padding() of it.
Code :
struct ContentView : View {
var body: some View {
VStack {
List(0..<5) { item in
HStack(alignment: VerticalAlignment.top, spacing: 5) {
Image(systemName: "photo")
VStack(alignment: HorizontalAlignment.leading, spacing: 10) {
Text("USA")
.font(.headline)
Text("This is an extremely long string that will never fit even the widest of Phones Excerpt From: Paul Hudson. “SwiftUI by Example”. Apple Books. ")
.lineLimit(nil)
.font(.subheadline)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.red)
.onAppear() {
print("on Appear")
}.onDisappear() {
print("on Disappear")
}
}.padding()
}
}