Swift UI stacked bordered views consistent border thickness - swift

In my Swift UI project, I have a number of vertically stacked views each with a black border. See this example visual:
Note that the top and bottom borders merge together to form a sort of thicker border. How do I make the merged top / bottom borders look the same thickness as the horizontal borders? In CSS I would simply make the thickness of the top and bottom borders half of the horizontal borders, but Swift doesn't have the capability of changing border widths for each edge as far as I know.
struct test: View {
var body: some View {
ScrollView {
ScrollViewReader { value in
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.id(i)
.border(Color.black, width: 4)
}
.onAppear() {
value.scrollTo(99)
}
}
}
}
}

On each edge, you can just overlay a Rectangle. The rectangles on the top and bottom are half the height, so when together, it is the same size as the width ones.
Code:
ScrollView {
ScrollViewReader { value in
VStack(spacing: 0) {
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.id(i)
.overlay(
Rectangle().frame(height: 2),
alignment: .top
)
.overlay(
Rectangle().frame(height: 2),
alignment: .bottom
)
.overlay(
Rectangle().frame(width: 4),
alignment: .leading
)
.overlay(
Rectangle().frame(width: 4),
alignment: .trailing
)
}
}
.onAppear() {
value.scrollTo(99)
}
}
}

A little bit of a hacky solution but managed to have the result with a negative spacing in a VStack wrapping the view
struct test: View {
var body: some View {
ScrollView {
ScrollViewReader { value in
VStack(spacing: -4){
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.id(i)
.border(Color.black, width: 4)
}
}
.onAppear() {
value.scrollTo(99)
}
}
}
}
}

I think the easiest way would be to add a negative padding. Just make the padding value half of the border width. Some other notes:
Make sure to add a VStack or LazyVStack inside your ScrollView - otherwise, you won't be able to have fine control over spacing, alignment, or layout.
I recommend LazyVStack because you have 100 Texts, which is a lot. When you have lots of elements such as Text, use LazyVStack to improve performance.
struct ContentView: View {
var body: some View {
ScrollView {
ScrollViewReader { value in
LazyVStack(spacing: 0) { /// better performance + set spacing to 0
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.id(i)
.border(Color.black, width: 4)
.padding(.vertical, -2) /// make this half of the border width
}
}
.onAppear() {
value.scrollTo(99)
}
}
}
}
}
Result:

Related

SwiftUI - Animate height of rectangle from 0 to height

I am attempting to animate the height of a RoundedRectangle() from 0 to its new height, I would like it to grow from its current position upwards when my device shakes. As it currently stands, I have a onShake function which I want to activate the growing of the rectangle.
struct View1: View {
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
Text("Hola")
.foregroundColor(.black)
.font(.headline)
RoundedRectangle(cornerRadius: 5)
.frame(width: 100, height: 0, alignment: .center)
.foregroundColor(Color("RedColour"))
.onShake {
withAnimation {
self.frame(width: 100, height: 100)
}
}
}
}
}
As you can probably tell I am very new to SwiftUI and have very limited knowledge of how animations work. Feel free to grill me for it, but I would also love some help with attempting to grow this rectangle upwards when my device shakes.
Much love!
You could try this. But as the information you provided do not include how you want to use this, this might be incomplete.
struct View1: View {
#State private var height: CGFloat = 0 // add this to modify
// the height of the Rounded Rectangle
let targetHeight: CGFloat = 100 // targetHeight var to syncronise with the VStack
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
Text("Hola")
.foregroundColor(.black)
.font(.headline)
.background{
VStack{
Spacer(minLength: 0)
RoundedRectangle(cornerRadius: 5)
.frame(width: 100, height: height)
.foregroundColor(.red)
.onShake {
withAnimation(.linear(duration: 5)) {
height = targetHeight
}
}
}.frame(height: targetHeight) // VStack with fixed height
//to animate from bottom up
}
}
}
}

How to horizontally center content of horizontal ScrollView?

I have a horizontal ScrollView, and in it an HStack, like so:
ScrollView(.horizontal) {
HStack {
Circle()
.frame(width: 60, height: 60)
...
}
With this code, the contents of the HStack are aligned to the left of the screen, but I would like to get them in the middle of the ScrollView. How can I accomplish this?
all you need to do is to define the size of ScrollView and the HStack so when you define the size of ScrollView and define the size of HStack the view and the important thing is to make the alignment HStack to the center so that the View like a circle inside the Hstack center in it
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack() {
Circle()
.frame(width: 60, height: 60)
// the alignment is center here
}.frame(width: 500, height: 60,alignment: .center)
}.frame(width: 500, height: 500)
}
}
here the alignment is leading so the circle go to left
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack() {
Circle()
.frame(width: 60, height: 60)
// the alignment is leading here
}.frame(width: 500, height: 60,alignment: .leading)
}.frame(width: 500, height: 500)
}
}
here the alignment is trailing so the circle go to right
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack() {
Circle()
.frame(width: 60, height: 60)
// the alignment is trailing here
}.frame(width: 500, height: 60,alignment: .trailing)
}.frame(width: 500, height: 500)
}
}
Assuming you want to center ScrollView's content when there is free space (because otherwise scrolling behaviour would be rather broken) here is a possible approach (w/o hardcode) - to use preference key to read scroll view's width on screen and apply it as a minimum width of content's container, the center alignment is applied by default.
Tested with Xcode 13.2 / iOS 15.2
struct DemoView: View {
#State private var scrollWidth = CGFloat.zero
var body: some View {
ScrollView(.horizontal) {
HStack {
Circle()
.frame(width: 60, height: 60)
}
.frame(minWidth: scrollWidth) // << here (cetner by default) !!
}
.border(Color.green) // << for demo
.background(GeometryReader {
// read width of ScrollView on screen
Color.clear
.preference(key: ViewWidthKey.self, value: $0.frame(in: .local).size.width)
})
.onPreferenceChange(ViewWidthKey.self) {
self.scrollWidth = $0 // << store width
}
}
}

Prevent view from overflowing in both directions

I'm trying to build a user-expandable view that will show more of its content by increasing its height. To accomplish this I will add .clipped() so that the content shown out of its bounds will be hidden, just like you would add overflow: hidden; in CSS.
However, it seems like by default VStack is centering its children, so when the height is smaller than the sum of the children's height, they overflow in both the top and the bottom.
Here is an example of what I'm talking about:
struct ExandableView: View {
var body: some View {
VStack(spacing: 0) {
Rectangle()
.fill(Color.red)
.frame(height: 50)
.padding(.horizontal)
Rectangle()
.fill(Color.green)
.frame(height: 50)
.padding(.horizontal)
}
.frame(height: 90)
.background(Color.blue)
}
}
Is there any way to make it behave so that the red item (the first) is always inside the blue container and the children can only overflow from the bottom?
Change frame alignment according to ExandableView. like this
struct ExandableView: View {
var body: some View {
// Other VStack code
}
.frame(height: 90, alignment: isExpand ? .top : .center) //<--Here
.background(Color.blue)
}
}

How can I create an "unfilled frame" in SwiftUI?

I'm sure this is something super-simple, but I cannot seem to figure it out. I am trying to create a "widget" that consists of three lines of text, stacked vertically. This information should be placed inside a "frame" or "border" so it resembles a card. There will be a row of these cards that will scroll horizontally.
Believe it or not, the only part of this I cannot figure out is how to draw the border around the widget. I've tried .border, but that snugs the border right up against the text. I know I can add padding, but what I really need is a fixed-size card so each element in the scrolling list is identical.
I've come closest using this:
.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.border(Color.blue)
.cornerRadius(20)
...but the corners are all clipped. For reference, here's the complete code listing:
struct AccountTile: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 8) {
Text("Account Balance").font(.largeTitle)
Text("Account Name").font(.headline)
HStack(spacing: 0) {
Text("There are ").font(.caption).foregroundColor(.gray)
Text("6 ").font(.caption).fontWeight(.bold).foregroundColor(.blue)
Text("unreconciled transactions.").font(.caption).foregroundColor(.gray)
}
}
.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.border(Color.blue)
.cornerRadius(20)
}
}
}
...and here's what that code is producing:
This is almost what I'm looking for - I just need the border to be complete.
Use some padding and overlay to create your border. Here is the code (:
struct AccountTile: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 8) {
Text("Account Balance").font(.largeTitle)
Text("Account Name").font(.headline)
HStack(spacing: 0) {
Text("There are ").font(.caption).foregroundColor(.gray)
Text("6 ").font(.caption).fontWeight(.bold).foregroundColor(.blue)
Text("unreconciled transactions.").font(.caption).foregroundColor(.gray)
}
}.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 2))
}
}
}

SwiftUI Alignment Bottom

I have the following code:
struct CircleView: View {
var body: some View {
ZStack(alignment: .bottom) {
VStack{
Spacer()
Circle().frame(width: UIScreen.main.bounds.width/5)
}
}
}
}
Why is it not aligning the circle on the bottom? I have a spacer which should take up all free space in the ZStack?
Thanks in advance.
Because Circle is a Shape and does not have own content size, so consumes all provided. You limited width, but not height, so Circle consumed all height (blue rect is all circle, but drown only where fit).
So if you want to make it aligned, you have to limit it completely, like below
ZStack(alignment: .bottom) {
VStack{
Spacer()
Circle()
.frame(width: UIScreen.main.bounds.width/5, height: UIScreen.main.bounds.width/5)
}
}