|
Hi all,
On the below screenshot you can see my layout on the watch. It's a simple table to show some scores. I don't think I have found an optimal way to code it, because I'm using a lot of .padding to center the name with the scores and with the total score at the bottom. On the smaller watch like version 6 it will not look as good as here. There must be a better way I can't see right now!
The question is about the optimal way to center each of the 3 columns, where the header with names and footer with total scores are always visible, but the middle part with scores is scrollable. The 3 visible columns at the end must be centered.
My code:
VStack {
HStack {
Text("Tomasz")
.font(.footnote)
.padding(.leading, 10)
Spacer()
Text("Adam")
.font(.footnote)
Spacer()
Text("Pawel")
.font(.footnote)
.padding(.trailing, 10)
}
Divider()
ScrollView (showsIndicators: false) {
HStack {
VStack {
ForEach (game.rounds) { round in
Text(String(round.scores[0]))
.padding(.leading, 30)
}
}
Spacer()
VStack {
ForEach (game.rounds) { round in
Text(String(round.scores[1]))
.padding(.leading, 5)
}
}
Spacer()
VStack {
ForEach (game.rounds) { round in
Text(String(round.scores[2]))
.padding(.trailing, 23)
}
}
}
}
Divider()
HStack {
Text(game.sumPoints(forUser: 0))
.font(.title3)
.bold()
.padding(.leading, 29)
Spacer()
Text(game.sumPoints(forUser: 1))
.font(.title3)
.bold()
.padding(.leading, 5)
Spacer()
Text(game.sumPoints(forUser: 2))
.font(.title3)
.bold()
.padding(.trailing, 22)
}
}
|
|
I don't know much about WatchOS but it seems like the exercises in Project 18 of Hacking with SwiftUI might be able to help you. It shows examples of how to create your own layout guides to align views with custom guide lines that you create.
So you could create your own guides named .column1Center, .column2Center, etc, and use those instead of .leading, .trailing, .top, etc.
|
|
|
|
@Hatsushira
I think Grid will not help me, because I need the header and footer to be always visible and only the middle part should scroll. I can't do this with Grid.
@Fly0strich
Nice suggestion and I had a lot of hope until I started to play with this... I made to get it work with a single guideline for middle player. The problem is that I would need 3 guidelines to center all the views for each of 3 players (3 columns). I thought I need to create 3 different extensions and name it differently to create 3 guidelines, but only a single guideline could be used in the top VStack... How to make 3 different guidelines is now the question.
Another problem is, that to make the middle part scrollable I need to put the middle HStacks with rounds into ScrollView and the guidelines are not working in this case. I have the player name and the total score aligned, but the points in the middle (which are in the ScrollView) are not alaigned anymore...
Anybody any more ideas?
My code:
extension HorizontalAlignment {
enum CenterMiddlePlayer: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[.trailing]
}
}
static let centerMiddlePlayer = HorizontalAlignment(CenterMiddlePlayer.self)
}
struct ContentView: View {
var body: some View {
VStack (alignment: .centerMiddlePlayer) {
// player names
HStack {
Text("Tomasz")
Text("Adam")
.alignmentGuide(.centerMiddlePlayer) { d in d[HorizontalAlignment.center]}
Text("Pawel")
}
// multiple rounds
HStack {
Text("1")
Text("0")
.alignmentGuide(.centerMiddlePlayer) { d in d[HorizontalAlignment.center]}
Text("0")
}
HStack {
Text("1")
Text("0")
.alignmentGuide(.centerMiddlePlayer) { d in d[HorizontalAlignment.center]}
Text("0")
}
// total points
HStack {
Text("2")
.bold()
Text("0")
.bold()
.alignmentGuide(.centerMiddlePlayer) { d in d[HorizontalAlignment.center]}
Text("0")
.bold()
}
}
}
}
Screenshot with ScrollView:
Screenshot without ScrollView:
|
|
@Hatsushira I think Grid will not help me, because I need the header and footer to be always visible and only the middle part should scroll. I can't do this with Grid.
Give it a try:
struct GridDemo: View {
var body: some View {
Grid() {
GridRow {
Text("Header")
}
ScrollView {
ForEach([0,1,2,3,4,5,6,7,8,9], id: \.self) { i in
GridRow {
Text("Row \(i)")
}
}
}
GridRow {
Text("Footer")
}
}
}
}
|
|
Sorry, I just had some time to mess around with your code a bit, and I think you're right. You would only be able to use 1 alignment guide for each Stack, and then choose a View in each stack that you used it on to align with a View in the other stack. So creating one guide for each column doesn't really help you in this case. My bad, I didn't understand how those guides worked very well when I suggested it.
But something else that you could do is use GeometryReader .
struct ContentView: View {
var body: some View {
GeometryReader { geo in
VStack () {
// player names
HStack {
Text("Tomasz")
.frame(width: geo.size.width / 3)
Text("Adam")
.frame(width: geo.size.width / 3)
Text("Pawel")
.frame(width: geo.size.width / 3)
}
// multiple rounds
HStack {
Text("1")
.frame(width: geo.size.width / 3)
Text("0")
.frame(width: geo.size.width / 3)
Text("0")
.frame(width: geo.size.width / 3)
}
HStack {
Text("1")
.frame(width: geo.size.width / 3)
Text("0")
.frame(width: geo.size.width / 3)
Text("0")
.frame(width: geo.size.width / 3)
}
// total points
HStack {
Text("2")
.bold()
.frame(width: geo.size.width / 3)
Text("0")
.bold()
.frame(width: geo.size.width / 3)
Text("0")
.bold()
.frame(width: geo.size.width / 3)
}
}
}
}
}
GeometryReader will basically just measure the maximum space available to whatever view you wrap inside of it, and allow you to use those measurements as a reference for where you want to place things, or how big you want them to be in reference to those measurements.
This is repeating the same .frame(width:) modifier a lot. But you wouldn't need to add it so many times if you were using ForEach to create the text fields in your actual app. But this will ensure that each Text view takes up exactly 1/3 of the available screen width. So the text views are all aligned perfectly.
|
|
@Hatsushira I've tried it, but forgot to write about the problem. The issue is that the GridRow inside a ScrollView behaves odd. If I have multiple Text views, in my case 3, each with point for an user, the row isn't created properly, but the Text views are below each other like in the VStack . Please check the screenshot. I've tried to use Group inside GridRow and other views, but it is not helping.
var body: some View {
Grid() {
GridRow {
Text("Tomasz")
Text("Adam")
Text("Pawel")
}
ScrollView {
ForEach([0,1,2,3,4,5,6,7,8,9], id: \.self) { i in
GridRow {
Text("\(i)")
Text("\(i)")
Text("\(i)")
}
}
}
GridRow {
Text("3")
Text("2")
Text("5")
}
}
}
|
|
|
|
@Fly0strich Your solution is working and I don't need to play with padding guessing appropriate value anymore. It will also automatically adapt to available space on watches with smaller screen sizes. After watching Compose custom layouts with SwiftUI proposed by Hatsushira there could be some other solution, but to be honest this video is too complicated for me at the stage where I currently am with SwiftUI and programming, so I will try to implement this GeometryReader stuff :). Thanks to all!
|