How to horizontally center content of horizontal ScrollView? - swift

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
}
}
}

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
}
}
}
}

SwiftUI - How to center a component in a horizontal ScrollView when tapped?

I have a horizontal ScrollView, and within it an HStack. It contains multiple Subviews, rendered by a ForEach. I want to make it so that when these Subviews are tapped, they become centered vertically in the view. For example, I have:
ScrollView(.horizontal) {
HStack(alignment: .center) {
Circle() // for demonstration purposes, let's say the subviews are circles
.frame(width: 50, height: 50)
Circle()
.frame(width: 50, height: 50)
Circle()
.frame(width: 50, height: 50)
}
.frame(width: UIScreen.main.bounds.size.width, alignment: .center)
}
I tried this code:
ScrollViewReader { scrollProxy in
ScrollView(.horizontal) {
HStack(alignment: .center) {
Circle()
.frame(width: 50, height: 50)
.id("someID3")
.frame(width: 50, height: 50)
.onTapGesture {
scrollProxy.scrollTo(item.id, anchor: .center)
}
Circle()
.frame(width: 50, height: 50)
.id("someID3")
.frame(width: 50, height: 50)
.onTapGesture {
scrollProxy.scrollTo(item.id, anchor: .center)
}
...
}
}
But it seemingly had no effect. Does anyone know how I can properly do this?
You can definitely do this with ScrollView and ScrollViewReader. However, I see a couple of things that could cause problems in your code sample:
You use the same id "someID3" twice.
I can't see where your item.id comes from, so I can't tell if it actually contains the same id ("someID3").
I don't know why you have two frames with the same bounds on the same view area. It shouldn't be a problem, but it's always best to keep things simple.
Here's a working example:
import SwiftUI
#main
struct MentalHealthLoggerApp: App {
var body: some Scene {
WindowGroup {
ScrollViewReader { scrollProxy in
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: 10) {
Color.clear
.frame(width: (UIScreen.main.bounds.size.width - 70) / 2.0)
ForEach(Array(0..<10), id: \.self) { id in
ZStack(alignment: .center) {
Circle()
.foregroundColor(.primary.opacity(Double(id)/10.0))
Text("\(id)")
}
.frame(width: 50, height: 50)
.onTapGesture {
withAnimation {
scrollProxy.scrollTo(id, anchor: .center)
}
}
.id(id)
}
Color.clear
.frame(width: (UIScreen.main.bounds.size.width - 70) / 2.0)
}
}
}
}
}
}
Here you can see it in action:
[EDIT: You might have to click on it if the GIF won't play automatically.]
Note that I added some empty space to both ends of the ScrollView, so it's actually possible to center the first and last elements as ScrollViewProxy will never scroll beyond limits.
Created a custom ScrollingHStack and using geometry reader and a bit calculation, here is what we have:
struct ContentView: View {
var body: some View {
ScrollingHStack(space: 10, height: 50)
}
}
struct ScrollingHStack: View {
var space: CGFloat
var height: CGFloat
var colors: [Color] = [.blue, .green, .yellow]
#State var dragOffset = CGSize.zero
var body: some View {
GeometryReader { geometry in
HStack(spacing: space) {
ForEach(0..<15, id: \.self) { index in
Circle()
.fill(colors[index % 3])
.frame(width: height, height: height)
.overlay(Text("\(Int(dragOffset.width))"))
.onAppear {
dragOffset.width = geometry.size.width / 2 - ((height + space) / 2)
}
.onTapGesture {
let totalItems = height * CGFloat(index)
let totalspace = space * CGFloat(index)
withAnimation {
dragOffset.width = (geometry.size.width / 2) - (totalItems + totalspace) - ((height + space) / 2)
}
}
}
}
.offset(x: dragOffset.width)
.gesture(DragGesture()
.onChanged({ dragOffset = $0.translation})
)
}
}
}

Increase/Decrease the size of a view horizontally by dragging the edges of it

I've seen a few similar examples of this such as How to correctly do up an adjustable split view in SwiftUI? and How to resize UIView by dragging from its edges? but I can't find exactly what I'm looking for that correlates correctly across to SwiftUI
I have a view that I want the user to be able to adjust the width of via a 'grab bar' on the right of the view. When the user drags this bar left (decreases view width) and to the right (increases the view width). How can I go about doing this?
In the example, RedRectangle is my view that i'm trying to adjust which comprises of a Rectangle and the Resizer which is manipulated to adjust the size. What am I doing wrong here?
Additionally, there isn't a gradual animation/transition of the frame being increased/decreased and it just seems to jump. How can I achieve this?
Reproducible example linked here:
import SwiftUI
struct ContentView: View {
#State var resizedWidth: CGFloat?
var body: some View {
HStack(alignment: .center) {
Spacer()
RedRectangle(width: 175, resizedWidth: resizedWidth)
Resizer()
.gesture(
DragGesture()
.onChanged({ value in
resizedWidth = max(80, resizedWidth ?? 0 + value.translation.width)
})
)
Spacer()
}
}
}
struct RedRectangle: View {
let width: CGFloat
var resizedWidth: CGFloat?
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: resizedWidth != nil ? resizedWidth : width, height: 300)
.frame(minWidth: 80, maxWidth: 400)
}
}
struct Resizer: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 8, height: 75)
.cornerRadius(10)
}
}
import SwiftUI
struct ContentView: View {
let minWidth: CGFloat = 100
#State var width: CGFloat?
var body: some View {
HStack(alignment: .center) {
Spacer()
RedRectangle(width: width ?? minWidth)
Resizer()
.gesture(
DragGesture()
.onChanged { value in
width = max(minWidth, width! + value.translation.width)
}
)
Spacer()
}
.onAppear {
width = minWidth
}
}
}
struct RedRectangle: View {
let width: CGFloat
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: width, height: 100)
}
}
struct Resizer: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 8, height: 75)
.cornerRadius(10)
}
}

Swift UI stacked bordered views consistent border thickness

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:

SwiftUI: How do I overlay an image in the center of the parent view?

I have an image that I'm placing over an existing view. It is flush left/leading but I want it centered on the screen (or the salmon colored area).
This is my code:
struct LeaderBoard: View {
#State var shouldDisplayOverLay = true
var body: some View {
ZStack {
VStack(alignment: .center) {
Text("Daily Dashboard")
.font(.subheadline)
Color.darkSalmon
Spacer()
HStack {
Text("Exercise")
Text("Sleep (in hours):")
Text("Weight") // red if lower more than a pound, yellow if less than pound, green if same or higher
}
.font(.caption)
Spacer()
}
Image("Leader-1024")
.resizable()
.scaledToFit()
.modifier(InsetViewModifier())
}
}
}
struct InsetViewModifier: ViewModifier {
func body(content: Content) -> some View {
GeometryReader { proxy in
content
.frame(width: proxy.size.width * 0.8, height: proxy.size.height * 0.8, alignment: .center)
}
}
}
GeometyReader does not have own alignment, so the solution can be to embed some stack in modifier (because all of them have .center alignment by default):
struct InsetViewModifier: ViewModifier {
func body(content: Content) -> some View {
GeometryReader { proxy in
VStack {
content
.frame(width: proxy.size.width * 0.8, height: proxy.size.height * 0.8, alignment: .center)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
and some replicated demo