How to make a SwiftUI view "Always on top"? - swiftui

I'm new to the XCode/SwiftUI scene and have a beginners question.
How can I tell SwiftUI to make a view "Always on top"?
I have a view with two buttons in it:
import SwiftUI
struct ContentView: View {
var body: some View {
HStack{
Button("Button1") {}
.padding(20)
.frame(width: 150, height: 40)
Button("x") {
NSApplication.shared.terminate(nil)
}
.padding(10)
.frame(width: 70, height: 40)
}
}
}
Thanks!

First, here is the code:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack { // I put everything in this VStack (vertical stack)
HStack{
Button("Button1") {}
.padding(20)
.frame(width: 150, height: 40)
Button("x") {
//
}
.padding(10)
.frame(width: 70, height: 40)
}
Spacer() // I added this spacer at the bottom to push everything to the top
}
}
}
Explanation
What is a VStack?
A VStack is a view that arranges its children in a vertical line.
What is a Spacer?
Spacer views automatically fill up all available space on their axis of expansion, which is a fancy way of saying they take up as much space as they can either horizontally or vertically, depending on what you put them in.
So basically:
When we put the HStack (horizontal stack) inside the VStack and add the Spacer() function at the bottom we push the HStack all the way to the top of the screen.

Related

Swiftui vertical paging tabview with scrollview inside

My goal is to have a vertical paginated tabview with a scrollview inside. Scrolling as soon as you finish the scrollview you pass to the other tab and if the content of the scrollview has a lower height than the screen, scrolling passes directly to the next tab.
var body: some View {
GeometryReader { proxy in
TabView {
ForEach(colors, id: \.self) { color in
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .bottom, spacing: 20) {
ForEach(0..<15) { i in
//GeometryReader { block in
VStack(alignment: .leading) {
Text("Block test test test test test test \(i)")
}
.rotationEffect(.degrees(-90))
.frame(width: 70, height: proxy.size.width, alignment: .leading)
.background(Color.green)
.id(i)
//}
//.offset(y: proxy.size.width / 2)
}
}
.frame(height: proxy.size.height)
.background(Color.blue)
}
.frame(width: proxy.size.height, height: proxy.size.width)
.background(Color.pink)
}
.frame(width: proxy.size.width,height: proxy.size.height)
}
.frame( width: proxy.size.height, height: proxy.size.width)
.rotationEffect(.degrees(90), anchor: .topLeading)
.offset(x: proxy.size.width)
.tabViewStyle(
PageTabViewStyle(indexDisplayMode: .never)
)
}
}
These are the steps I followed:
created a tabview with horizontal scrolls inside
Rotated the tabview by 90°
Rotated the Vstacks inside the scrollview by -90°
The result is exact and the scrolling of the contents is continuous passing smoothly between scroll and tab, but the only problem is that I can't control the dimensions of the Vstacks inside the scrollview and therefore I can't have Vstacks with different heights in based on the content.
I tried to add a GeometryReader { block for the VStacks but besides not giving me the correct measurements of the VStacks it breaks the layout completely.
How can I get the dimensions of each Vstack correctly?

Why do the views extend wider than the screen?

Edit: Substitute your "system name:" of choice. "pencil.circle" works fine. "edit" is not a valid SF Symbol.
(I've simplified my code so you can cut and paste. That's why you see .frame, resizable, etc. where much simpler code might your first instinct.)
I have created a view which is a vertical list of row items (table view).
Each row item has a horizontal view with two images inside it.
The images take up too much space and do not fit correctly on the screen:
import SwiftUI
#main
struct StackOverflowDemoApp: App {
var body: some Scene {
WindowGroup {
TandemView()
}
}
}
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
Spacer()
}
}
struct TandemView: View {
var body: some View {
HStack {
Spacer()
Image(systemName: "pencil")
.resizable()
.background(Color.orange)
.frame(height: 80)
.aspectRatio(1, contentMode: .fill)
PaddedImageView()
.frame(width: 200, height: 80)
}
.padding()
.fixedSize()
}
}
struct TandemView_Previews: PreviewProvider {
static var previews: some View {
TandemView()
}
}
The above is the closest I can get to the desired layout (it just needs to fit horizontally). I experimented with GeometryReader but that did not produce desired results.
Here are some things I tried:
The code as provided
NoConstraintsOnPencilOrHStack
NoConstraintsOnTandemView
NoConstraintsOnImageInPaddedViewButWithFrameConstraint
I am trying to get a row view which consists of two Images (my actual source consists of UIImage objects) that fits within the width of the screen.
Edit:
After Accepting cedricbahirwe's spot-on response, I was able to simplify the code further. New results:
I added at the top level
TandemView()
.padding(.horizontal)
I removed:
// Spacer()
at the end of PaddedImageView
updated TandemView -- changed both frames and removed 3 lines:
struct TandemView: View {
var body: some View {
HStack {
Spacer()
Image(systemName: "pencil")
.resizable()
.background(Color.orange)
.frame(width: 80, height: 80)
// .aspectRatio(1, contentMode: .fill)
PaddedImageView()
.frame(height: 80)
}
// .padding()
// .fixedSize()
}
}
This is happening because of the layout of PaddedImageView View, you can actually remove the Spacer since it is not needed there.
So change
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
Spacer()
}
}
to
struct PaddedImageView: View {
let color: Color = .red
var body: some View {
ZStack {
color
Image(systemName: "edit")
.resizable()
.padding()
}
}
}
Note:
SwiftUI Engine infers the layout of your view from the implementation of the body property. It's recommended to have one Parent View inside the body property.

SwiftUI, remove space between views in a VStack?

Why is there so much space between the three blue rectangles and the list? How can I remove the space so that all views within the VStack stack at the top? I tried using a Spacer() directly after the List, but nothing changed.
struct ContentView: View {
init() { UITableView.appearance().backgroundColor = UIColor.clear }
var body: some View {
NavigationView {
ZStack {
Color.red
.ignoresSafeArea()
VStack {
HStack {
Text("Faux Title")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.heavy)
Spacer()
Button(action: {
// settings
}, label: {
Image(systemName: "gearshape.fill")
.font(.system(.title2))
})
}
.padding()
GeometryReader { geometry in
HStack() {
Text("1")
.frame(width: geometry.size.width * 0.30, height: 150)
.background(Color.blue)
Spacer()
Text("2")
.frame(width: geometry.size.width * 0.30, height: 150)
.background(Color.blue)
Spacer()
Text("3")
.frame(width: geometry.size.width * 0.30, height: 150)
.background(Color.blue)
}
}
.padding()
List {
Text("One")
Text("Two")
Text("Three")
Text("Four")
Text("Five")
Text("Six")
}
.listStyle(InsetGroupedListStyle())
}
}
.navigationBarHidden(true)
}
}
}
Bonus question: In web development, you can open your browser's Web Inspector and use the element selector to click on elements which highlights their borders. Useful for something like this where you're trying to figure out which element the offending spacing belongs to. Is there something like that in Xcode?
VStack(spacing: 0) {...}
Spacer()
to your question you can in Xcode use the view inspector. https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ExaminingtheViewHierarchy.html
Since you know that your HStack with the blue rectangles is going to be a height of 150, you should constrain it to that using .frame(height: 150):
GeometryReader { geometry in
...
}
.padding()
.frame(height: 150) //Here
Otherwise, the GeometryReader will occupy all available vertical space.
Re: your web dev comparison, check out the Xcode view hierarchy inspector. It's not exactly the same, but it's in the same vein: https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ExaminingtheViewHierarchy.html

SwiftUI height animation of a text frame when #ObservedObject change

I try to make a smooth animation when the NetStatus change but it's not working like i want.
I want to get the same effect as when i press the button with the toggle animation. The commented button animation is working great and i try to replicate it with the scaling of the height of the text frame.
The commented button code is just for a working example of the animation effect that i want (expand and close gracefully), i don't need this code.
How can i do that?
import SwiftUI
struct NoNetwork: View {
let screenSize: CGRect = UIScreen.main.bounds
#ObservedObject var online = NetStatus()
var body: some View {
VStack{
Text("NoNetworkTitle")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: self.online.connected ? 0 : 40, alignment: .center)
// .animation(.easeIn(duration: 5))
.background(Color.red)
// Button(action: {
// withAnimation {
// self.online.connected.toggle()
// }
// }, label: {
// Text("Animate")
// })
}
}
}
struct NoNetwork_Previews: PreviewProvider {
static var previews: some View {
NoNetwork()
}
}
To animate when online.connected changes, put the .animation modifier on the VStack:
VStack{
Text("NoNetworkTitle")
.fontWeight(.bold)
.foregroundColor(Color.white)
.frame(width: screenSize.width, height: self.online.connected ? 0 : 40, alignment: .center)
.background(Color.red)
Button(action: {
self.online.connected.toggle()
}, label: {
Text("Animate")
})
}
.animation(.easeInOut(duration: 0.5))
This will animate the other views in the VStack as the Text appears and disappears.

Ignore horizontal safe area with vertical ScrollView

I am trying to create a layout with multiple horizontal ScrollViews inside a vertical ScrollView, similar to the template picker in Apple's Pages app. I would like the content of the horizontal ScrollViews to be visible beyond the safe area. However I seem to be unable to get the content of the vertical ScrollView outside the horizontal safe insets. This is visible when iPhones with a notch are used in landscape orientation.
I have tried adding negative padding to the content of the vertical ScrollView. This kind of works, but creates issues when using the device in portrait mode.
Below example code shows the issue. I would expect the rectangles to be visible in beyond the safe area when scrolling horizontally, but they get clipped. How can I make them visible beyond the safe area?
import SwiftUI
struct ContentView: View {
var body: some View {
ScrollView(.vertical) {
ScrollView(.horizontal) {
HStack {
Rectangle()
.frame(width: 200, height: 300)
Rectangle()
.frame(width: 200, height: 300)
Rectangle()
.frame(width: 200, height: 300)
}
} .edgesIgnoringSafeArea(.horizontal)
} .edgesIgnoringSafeArea(.horizontal)
}
}
You can detect when the device's orientation changes and adapt your view:
struct ContentView: View {
#Environment(\.verticalSizeClass) var verticalSizeClass
var body: some View {
Group {
if verticalSizeClass == .compact {
content.edgesIgnoringSafeArea(.horizontal)
} else {
content
}
}
}
var content: some View {
ScrollView(.vertical) {
ScrollView(.horizontal) {
HStack {
Rectangle()
.frame(width: 200, height: 300)
Rectangle()
.frame(width: 200, height: 300)
Rectangle()
.frame(width: 200, height: 300)
}
}
}
.edgesIgnoringSafeArea(.horizontal)
}
}