I am using SF Symbols in an app and discovering that they all render at different sizes, which makes lists of elements hard to align. What is the best way to make sure SF Symbols display at the same size, so they all line up and other elements line up too. I am sure I could hard code the frame width and height, but then that would break dynamic types, which I would like to keep.
Here is some sample code I wrote to demonstrate this:
struct LayoutTest: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .top) {
Image(systemName: "figure.walk").background(Color.red)
Text("Value 1").background(Color.red)
}
HStack(alignment: .top) {
Image(systemName: "phone").background(Color.red)
Text("Value 2").background(Color.red)
}
HStack(alignment: .top) {
Image(systemName: "figure.step.training").background(Color.red)
Text("Value 3").background(Color.red)
}
}.font(.title)
}
}
You can hard code a width and height and have it adjust via dynamic type by using #ScaledMetric, which adjusts a value based on the dynamic type size:
struct LayoutTest: View {
#ScaledMetric var width: CGFloat = 50
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .center) {
Image(systemName: "figure.walk").background(Color.red)
.frame(width: width, alignment: .center)
Text("Value 1").background(Color.red)
}
HStack(alignment: .center) {
Image(systemName: "phone").background(Color.red)
.frame(width: width, alignment: .center)
Text("Value 2").background(Color.red)
}
HStack(alignment: .center) {
Image(systemName: "figure.wave").background(Color.red)
.frame(width: width, alignment: .center)
Text("Value 3").background(Color.red)
}
}.font(.title)
}
}
Standard and XXXL sizes look like this:
Related
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)
}
}
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()
}
}
I have a simple Rectangle which I put a VStack of Numbers in background of that, you can see it in code and pic! the problem is the VStack is taller than Rectangle there for it goes outside of Rectangle, I want the extra part be hidden, How could I solve it?
struct ContentView: View {
let arrayOfHours: [Int] = Array(0...23)
var body: some View {
Rectangle()
.fill(Color.black.opacity(0.25))
.frame(height: 200, alignment: .center)
.padding()
.background(
VStack(alignment: .center, spacing: 4) {
ForEach (arrayOfHours.indices, id: \.self) { index in
Text(arrayOfHours[index].description)
.font(Font.body.bold())
}
}
.background(Color.yellow)
)
}
}
If I understood your description correctly you need clipped, like
struct ContentView: View {
let arrayOfHours: [Int] = Array(0...23)
var body: some View {
Rectangle()
.fill(Color.black.opacity(0.25))
.frame(height: 200, alignment: .center)
.background(
VStack(alignment: .center, spacing: 4) {
ForEach (arrayOfHours.indices, id: \.self) { index in
Text(arrayOfHours[index].description)
.font(Font.body.bold())
}
}
.background(Color.yellow)
)
.clipped() // << here !!
.padding() // should be moved here !!
}
}
I have a SwiftUI view that is a circular view which when tapped opens up and is supposed to extend over the UI to its right. How can I make sure that it will appear atop the other ui? The other UI elements were created using a ForEach loop. I tried zindex but it doesn't do the trick. What am I missing?
ZStack {
VStack(alignment: .leading) {
Text("ALL WORKSTATIONS")
ZStack {
ChartBackground()
HStack(alignment: .bottom, spacing: 15.0) {
ForEach(Array(zip(1..., dataPoints)), id: \.1.id) { number, point in
VStack(alignment: .center, spacing: 5) {
DataCircle().zIndex(10)
ChartBar(percentage: point.percentage).zIndex(-1)
Text(point.month)
.font(.caption)
}
.frame(width: 25.0, height: 200.0, alignment: .bottom)
.animation(.default)
}
}
.offset(x: 30, y: 20)
}
.frame(width: 500, height: 300, alignment: .center)
}
}
}
}
.zIndex have effect for views within one container. So to solve your case, as I assume expanded DataCircle on click, you need to increase zIndex of entire bar VStack per that click by introducing some kind of handling selection.
Here is simplified replicated demo to show the effect
struct TestBarZIndex: View {
#State private var selection: Int? = nil
var body: some View {
ZStack {
VStack(alignment: .leading) {
Text("ALL WORKSTATIONS")
ZStack {
Rectangle().fill(Color.yellow)//ChartBackground()
HStack(alignment: .bottom, spacing: 15.0) {
ForEach(1...10) { number in
VStack(spacing: 5) {
Spacer()
ZStack() { // DataCircle()
Circle().fill(Color.pink).frame(width: 20, height: 20)
.onTapGesture { self.selection = number }
if number == self.selection {
Text("Top Description").fixedSize()
}
}
Rectangle().fill(Color.green) // ChartBar()
.frame(width: 20, height: CGFloat(Int.random(in: 40...150)))
Text("Jun")
.font(.caption)
}.zIndex(number == self.selection ? 1 : 0) // << here !!
.frame(width: 25.0, height: 200.0, alignment: .bottom)
.animation(.default)
}
}
}
.frame(height: 300)
}
}
}
}
i have an image that i want to strech only it's height to fit different content, how do i do that in swiftUI? right now it looks like this
struct ContentView: View {
var body: some View {
VStack {
HStack {
VStack(alignment: .leading, spacing: 130) {
Text("Title")
.font(.headline)
.foregroundColor(.primary)
Text("text")
.font(.subheadline)
.foregroundColor(.secondary)
Text("padding")
}
.padding(.vertical)
Spacer()
Image("rightTag")
.resizable(capInsets: .init(top: 0, leading: 0, bottom: 0, trailing: 0), resizingMode: .stretch)
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 20)
}
.frame(maxWidth: screen.width - 60)
.padding(.leading)
.background(.white)
.cornerRadius(20)
}
}
}
how can i stretch its height to fit this outer frame? ragular resizable and stuff can't get it done.
any helped would be wonderful! Thanks!
sry i didn't make myself clear earllier.
Here is possible approach. However as I see now you'd rather need to stretch not the entire original image, but only middle of it, so in real project it would be needed to make your image tri-part and apply below stretching approach only to middle (square) part.
Approach uses asynchronous state update, so works in Live Preview / Simulator / RealDevice. (Tested with Xcode 11.2 / iOS 13.2)
struct ContentView: View {
#State private var textHeigh: CGFloat = .zero
var body: some View {
VStack {
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 130) {
Text("Title")
.font(.headline)
.foregroundColor(.primary)
Text("text")
.font(.subheadline)
.foregroundColor(.secondary)
Text("padding")
}
.padding(.vertical)
.alignmentGuide(.top, computeValue: { d in
DispatchQueue.main.async {
self.textHeigh = d.height // !! detectable height of left side text
}
return d[.top]
})
Spacer()
Image("rightTag")
.resizable()
.frame(maxWidth: 20, maxHeight: max(60, textHeigh)) // 60 just for default
}
.frame(maxWidth: screen.width - 60)
.padding(.leading)
.background(Color.white)
}
}
}