Why does the HStack make the layout render wrongly? - swiftui

I'm trying to compose a view using a VStack that nests a HStack and a grid (https://github.com/spacenation/swiftui-grid). The expected outcome is that the HStack and all grid items should render. The actual outcome is that only one item of the grid renders. But when I change the HStack to VStack.The view renders as expected. Pls note that the grid renders a Geometry Reader which nests a ZStack Here's my code and an image. The blue frame in the image is the grid. It shrinked in height.
ScrollView {
VStack(spacing: 0) {
HStack {
ImageView(urlString: featuredItem.imageUrl, id: featuredItem.id)
VStack( alignment: .leading, spacing: 0) {
Text(featuredItem.title)
.padding(.vertical, 16)
.font(.custom("AbrilFatface-Regular", size: 48))
.fixedSize(horizontal: false, vertical: true)
Rectangle()
.fill(Color.red)
.frame(width: 96, height: 8)
Text(featuredItem.quote)
.font(.custom("Rubik-Regular", size: 14))
.padding(.top, 16)
.padding(.bottom, 32)
HStack {
ImageView(urlString: featuredItem.storeAvatarUrl, id:
featuredItem.id, width: 32, height: 32, isClipped: true)
Text(featuredItem.storeName)
.font(.custom("Rubik-Medium", size: 14))
}
}
.frame(width: 246)
}
.overlay(
GeometryReader { proxy in
VStack {
Text(self.featuredItem.price)
.frame(width: 72, height: 48)
}
.frame(width: proxy.size.width, height: proxy.size.height,
alignment: .topTrailing)
}
)
ShrineGrid(columns: 2, rowsHeight: .fixed(250))
}
}.edgesIgnoringSafeArea(.all)

Related

Variable Rectangle() dimension based on cell size to draw a timeline

I am trying to build a List that I want to look like a timeline.
Each cell will represent a milestone.
Down the left hand side of the table, I want the cells to be 'connected', by a line (the timeline).
I have tried various things to get it to display as I want but I have settled with basic geometric shapes , i.e Circle() and Rectangle().
This is sample code to highlight the problem:
import SwiftUI
struct ContentView: View {
var body: some View {
let roles: [String] = ["CEO", "CFO", "Managing Director and Chairman of the supervisory board", "Systems Analyst", "Supply Chain Expert"]
NavigationView{
VStack{
List {
ForEach(0..<5) { toto in
NavigationLink(
destination: dummyView()
) {
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .center, spacing: 0){
Rectangle()
.frame(width: 1, height: 30, alignment: .center)
Circle()
.frame(width: 10, height: 10)
Rectangle()
.frame(width: 1, height: 20, alignment: .center)
Circle()
.frame(width: 30, height: 30)
.overlay(
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.system(size: 30, weight: .light , design: .rounded))
.frame(width: 30, height: 30)
)
//THIS IS THE RECTANGLE OBJECT FOR WHICH I WANT THE HEIGHT TO BE VARIABLE
Rectangle()
.frame(width: 1, height: 40, alignment: .center)
.foregroundColor(.green)
}
.frame(width: 32, height: 80, alignment: .center)
.foregroundColor(.green)
VStack(alignment: .leading, spacing: 0, content: {
Text("Dummy operation text that will be in the top of the cell")
.font(.subheadline)
.multilineTextAlignment(.leading)
.lineLimit(1)
Label {
Text("March 6, 2021")
.font(.caption2)
} icon: {
Image(systemName: "calendar.badge.clock")
}
HStack{
HStack{
Image(systemName: "flag.fill")
Text("In Progress")
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.blue)
.background(Color.white)
.cornerRadius(5, antialiased: true)
HStack{
Image(systemName: "person.fill")
Text(roles[toto])
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.green)
.background(Color.white)
.cornerRadius(5, antialiased: true)
HStack{
Image(systemName: "deskclock")
Text("in 2 Months")
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.red
)
.background(
Color.white
)
.cornerRadius(5, antialiased: true)
}
})
}.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct dummyView: View {
var body: some View {
Text("Hello, World!")
}
}
struct dummyView_Previews: PreviewProvider {
static var previews: some View {
dummyView()
}
}
but as you can see in the enclosed picture, there are unwanted gaps
So other content in the cell is making the height of the entire cell 'unpredictable' and break the line.
Is there a way to determine the height of the cell and extend the dimensions of the Rectangle, so that it extends to the full height of the cell?
Is there a better approach you recommend for trying to build such a timeline ?
PS: I have tried playing around with .frame and .infinity but that does work.
Many thanks.
Why not just draw the line based on the size of the row. See Creating custom paths with SwiftUI. Remember, everything is a view.
First, you need to decompose what you are doing into subviews. You have too many moving parts in one view to get it correct. Also, I would avoid setting specific padding amounts as that will mess you up when you change devices. You want a simple, smart view that is generic enough to handle different devices.
I would have a row view that has a geometry reader so it knows its own height. You could then draw the line so that it spanned the full height of the row, regardless of the height. Something along the lines of this:
struct ListRow: View {
var body: some View {
GeometryReader { geometry in
ZStack {
HStack {
Spacer()
Text("Hello, World!")
Spacer()
}
VerticalLine(geometry: geometry)
}
}
}
}
and
struct VerticalLine: View {
let geometry: GeometryProxy
var body: some View {
Path { path in
path.move(to: CGPoint(x: 20, y: -30))
path.addLine(to: CGPoint(x: 20, y: geometry.size.height+30))
}
.stroke(Color.green, lineWidth: 4)
}
}

Center Image in VStack leading alignment

I'm having troubles figuring out the proper solution to centre an Image inside a VStack with an alignment of .leading. I've attempted and got these results but is there a more efficient way instead of adding two Spacer()'s in an HStack?
struct ContentView: View {
var body: some View {
ZStack {
ScrollView {
VStack (alignment: .leading, spacing: 10) {
Text("Title ")
HStack {
Spacer()
Image(systemName:"star.fill")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
Spacer()
}
Divider()
}
}
}
.padding(.all)
}
}
Here is a solution. Tested with Xcode 12.1 / iOS 14.1
ScrollView {
VStack (alignment: .leading, spacing: 10) {
Text("Title ")
Image(systemName:"star.fill")
.resizable()
.frame(width: 20, height: 20)
.frame(maxWidth: .infinity, alignment: .center)
Divider()
}
}

How to detect whether the scrollview was scrolled or not in SwiftUI?

I have a horizontal ScrollView and would like to provide a function that when the first element is not visible anymore an arrow should appear to signalize that there are some other elements.
I've tried the DragGesture() to read the translation.width but it seems to be buggy in combination with the ScrollView.
So I'm looking for a way to detect whether the ScrollView was scrolled and how far. Is there any way?
private let gridItems = [GridItem(.flexible())]
ScrollView(.horizontal, showsIndicators: false) {
HStack{
LazyHGrid(rows: gridItems){
ForEach(viewModel.imageOptions, id: \.self){ image in
ZStack {
Image(image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.background(Color.white)
.clipShape(Circle())
.background(
Circle()
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 5.0, y: 5.0)
)
.onTapGesture {
print("selected")
}
.padding(15)
Circle()
.strokeBorder(Color.orange, lineWidth: 5)
.frame(width: 70, height: 70)
}
}
}
}
}
}

VStack .leading alignment

I can't get rid of this "leading" padding inside one of my VStacks.
I set alignment to "leading" for its parent VStack but it still shows some spacing on the left (for the text and red rounded rectangle). They were supposed to be placed on the left next to the blue edge on screenshot.
Any ideas on why this padding appears there?
Here's the code:
import SwiftUI
struct RegisterView: View {
var body: some View {
VStack(spacing: 0) {
HStack(alignment: .top) {
Text("Register")
.font(.custom("NotoSans-Regular", size: 24))
.fontWeight(.bold)
.foregroundColor(Color.tertiaryTitleColor)
}
.frame(width: 299, height: 39)
.padding(.top, 78)
HStack(spacing: 13) {
BroviderButton(imageName: "googleLogo")
BroviderButton(imageName: "facebookLogo")
}
.padding(.top, 47)
VStack(alignment: .leading, spacing: 0) {
Text("Register with E-mail")
.font(.custom("NotoSans-Regular", size: 16))
RoundedRectangle(cornerRadius: 25)
.fill(Color.red)
.frame(width: 40, height: 20)
}
.frame(width: 279)
.padding(.top, 61)
Spacer()
}
.frame(maxHeight: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/)
.frame(maxWidth: /*#START_MENU_TOKEN#*/.infinity/*#END_MENU_TOKEN#*/)
.background(Color.loginBackgroundColor)
.ignoresSafeArea()
}
}
struct RegisterView_Previews: PreviewProvider {
static var previews: some View {
RegisterView()
}
}
struct BroviderButton: View {
var imageName: String
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 27.5)
.frame(width: 133, height: 56, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.shadow(color: Color.dropShadowColor, radius: 1, x: 10, y: 20)
.offset(y: 15)
.opacity(0.2)
.blur(radius: 20)
.opacity(0.2)
VStack {
VStack {
Image(imageName)
.resizable()
.scaledToFit()
}
.frame(height: 28, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
}
.frame(width: 133, height: 56, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.foregroundColor(/*#START_MENU_TOKEN#*/.blue/*#END_MENU_TOKEN#*/)
.background(Color.loginBackgroundColor)
.cornerRadius(27.5)
.opacity(1)
}
}
}
You need also apply alignment to frame of container, like
VStack(alignment: .leading, spacing: 0) {
Text("Register with E-mail")
.font(.custom("NotoSans-Regular", size: 16))
RoundedRectangle(cornerRadius: 25)
.fill(Color.red)
.frame(width: 40, height: 20)
}
.frame(width: 279) // << do you really need this? why?
.frame(maxWidth: .infinity, alignment: .leading) // << here !!
.padding(.top, 61)

Make 2x2 grid based on screen width

Sorry if this is a simple question but I'm just starting out with SwiftUI and I'm trying to figure out how to make a 2x2 grid of views based on the width of the screen. Meaning that each square has a width and height of have the screen width and they are arranged in a 2x2 grid with no padding.
I've been trying with two HStacks with two views in each placed on top of each other but the view size inside the HStack seems to dictate the HStack's height.
Code for Views I'm trying to arrange into the 2x2 grid:
var body: some View {
VStack {
TextField("0", text: $value)
.multilineTextAlignment(.center)
.textFieldStyle(PlainTextFieldStyle())
.font(.system(size: 40, weight: .semibold, design: .rounded))
.border(Color.black)
.padding()
Text(title)
.padding([.leading, .bottom, .trailing])
.font(.system(size: 14, weight: .regular, design: .rounded))
}
.background(Color.green)
.cornerRadius(10)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.black, lineWidth: 5))
}
I find that using A combination of HStacks, VStacks and aspectRatio(_ aspectRatio: CGFloat? = nil, contentMode: ContentMode) works best for forcing certain proportions on views:
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 0) {
Rectangle().fill(Color.red)
.aspectRatio(1.0, contentMode: .fit)
Rectangle().fill(Color.green)
.aspectRatio(1.0, contentMode: .fill)
}
HStack(spacing: 0) {
Rectangle().fill(Color.blue)
.aspectRatio(1.0, contentMode: .fit)
Rectangle().fill(Color.yellow)
.aspectRatio(1.0, contentMode: .fill)
}
}.aspectRatio(contentMode: .fit)
}
}
Results in a layout like this:
This can be done using a GeometryReader:
struct ContentView: View
{
var body: some View
{
GeometryReader
{ geometry in
self.useProxy(geometry)
}
}
func useProxy(_ geometry: GeometryProxy) -> some View
{
let dimension = min(geometry.size.width, geometry.size.height)
return VStack
{
HStack(spacing: 0)
{
Text("Top Left")
.frame(width: dimension / 2, height: dimension / 2)
.border(Color.black)
Text("Top Right")
.frame(width: dimension / 2, height: dimension / 2)
.border(Color.black)
}
HStack(spacing: 0)
{
Text("Bottom Left")
.frame(width: dimension / 2, height: dimension / 2)
.border(Color.black)
Text("Bottom Right")
.frame(width: dimension / 2, height: dimension / 2)
.border(Color.black)
}
}
}
}
Watch (the first part of) this WWDC video to hear about the layout system of SwiftUI.