Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to style rows in SwiftUI List on WatchOS?

I'm working with SwiftUI and is trying to make a list of buttons with custom .buttonStyle inside a List view on WatchOS, but can't get it to work. Is this even currently possible, and if so, how?

Example project:

struct Superhero: Identifiable {
    var id = UUID()
    var name: String
}

var superheroes = [
    Superhero(name: "Batman"),
    Superhero(name: "Superman"),
    Superhero(name: "Wonder Woman"),
    Superhero(name: "Aquaman"),
    Superhero(name: "Green Lantern"),
    Superhero(name: "The Flash")
]

struct SuperheroButtonStyle: ButtonStyle {

  func makeBody(configuration: Self.Configuration) -> some View {
    configuration.label
        .frame(minWidth: 0, maxWidth: .infinity)
        .foregroundColor(.white)
        .background(configuration.isPressed ? Color.white.opacity(0.95) : .green)
        .cornerRadius(13.0)
  }
}

struct SuperHeroesView: View{
    var body: some View {
        List {

            ForEach(superheroes) { superhero in
                Button(action: {
                    print(superhero.name)
                }){
                    Text(superhero.name)
                }.buttonStyle(SuperheroButtonStyle())
           }

       }
    }
}

This code produces this: picture of WatchOS app

Instead, I want the buttons to look more like this:

picture of wanted result

In this last example, I'm using ScrollView instead of List which means I'm not getting the nice animation of rows shrinking and fading away when scrolling through the list.

I've tried playing with negative padding, but that can't give me a bigger corner radius (more round corners than the default) and doesn't really look like good.

So is there a way to apply a ButtonStyle on items in a List view correctly, including having a custom cornerRadius?

Or is there some other way of getting more control of the way the rows look while still keeping the animation that comes with List?

like image 813
Daniel Duvanå Avatar asked Oct 17 '19 14:10

Daniel Duvanå


Video Answer


4 Answers

I don't know if you've figured this out yet or not, but I ended up using

List {

        ForEach(superheroes) { superhero in
            Button(action: {
                print(superhero.name)
            }){
                Text(superhero.name)
            }
            .buttonStyle(SuperheroButtonStyle())
            .listRowInsets(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 0))
       }

   }

to extend the edges of my buttons to the edge of the list like this:

Rows with extended edges

You can also add the modifier

.environment(\.defaultMinListRowHeight, 20)

to the List itself to allow the rows height to scale down to the size you want. I chose 20 for this example:

Correctly sized row height

And finally there's a

.listRowPlatterColor(.clear)

modifier you can use to change the background color of the row itself. I chose clear for this example to hide the edges that aren't fully covered by the button.

Clear Platter

like image 187
jgramse Avatar answered Oct 17 '22 23:10

jgramse


.listRowPlatterColor(.clear) Gave me what I was looking for. That was probably harder than it needed to be to find that.

struct ContentView: View {
    @State private var EnrouteText = "Enroute"
    var body: some View {
        List {
            Button(action: {
                self.EnrouteText = getCurrentTime()
            }) {
                Text(EnrouteText)
            }
            .buttonStyle(BtnStyle(bgColor: .red, fgColor: .white))
            .listRowInsets(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 0))
            .listRowPlatterColor(.clear)

            // The rest of the buttons.
        }
    }
}

// Button Styles
struct BtnStyle: ButtonStyle {
    var bgColor: Color
    var fgColor: Color

    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .frame(minWidth: 0, maxWidth: .infinity)
            .padding()
            .foregroundColor(fgColor)
            .background(bgColor)
            .cornerRadius(32)
    }
}

Example of using listRowPlatterColor to clear the final part of the background

like image 39
Mark Tomlin Avatar answered Oct 17 '22 22:10

Mark Tomlin


According to the Human Interface Guidelines, it's recommended that buttons within a scrolling view (e.g. List) look like a rounded rectangle (rather than a capsule).

If you attempt to use .listRowBackground, you'll get a sharp rectangle (not desired):

enter image description here

If you instead use .listRowPlatterColor, you'll get exactly what Apple recommends you use (in one line of code, nonetheless):

enter image description here

Button("Log out") {}
  .listRowPlatterColor(Color.blue)
like image 28
Senseful Avatar answered Oct 17 '22 23:10

Senseful


To have a the background of the whole cell in a certain colour you have to use .listRowBackground modifier. You can either use a Capsule or a RoundedRectangle as the background depending on your preference:

struct SuperHeroesView: View{
    var body: some View {
        List {
            ForEach(superheroes) { superhero in
                Button(action: { print(superhero.name) }){
                    Text(superhero.name)
                }   .buttonStyle(SuperheroButtonStyle())
            }   .listRowBackground(Capsule()
                                       .fill(Color.green))
                // or as an alternative:
                //.listRowBackground(RoundedRectangle(cornerRadius: 13.0)
                //                       .fill(Color.green))
       }
    }
}

You would also probably want to change the button style, as the background highlight effect will only affect the button, not the whole cell:

struct SuperheroButtonStyle: ButtonStyle {

  func makeBody(configuration: Self.Configuration) -> some View {
    configuration.label
        .frame(minWidth: 0, maxWidth: .infinity)
        .foregroundColor(configuration.isPressed ? .gray : .white)
  }
}
like image 2
LuLuGaGa Avatar answered Oct 18 '22 00:10

LuLuGaGa