Skip to content

Instantly share code, notes, and snippets.

@helje5
Last active October 16, 2023 21:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save helje5/0d8013dc40bf435b23d0074bce31af29 to your computer and use it in GitHub Desktop.
Save helje5/0d8013dc40bf435b23d0074bce31af29 to your computer and use it in GitHub Desktop.
ListCellSizingFlicker
import SwiftUI
struct Item: Identifiable {
let id = UUID()
let title = "Hello"
}
let items = [
Item(), Item(), Item(), Item(), Item(), Item(), Item(), Item(), Item(), Item(), Item(),
Item(), Item(), Item(), Item(), Item(), Item(), Item(),
]
struct ContentView: View {
@State private var animated = true
var body: some View {
NavigationStack {
Toggle("Animated", isOn: $animated).padding()
List {
ForEach(items) { item in
Cell(item: item, animated: animated)
}
}
}
}
struct Cell : View {
@State private var isExpanded = false
let item : Item
let animated : Bool
private func toggle() {
if animated {
withAnimation { isExpanded.toggle() }
}
else {
isExpanded.toggle()
}
}
var body: some View {
Label {
VStack {
HStack {
Text(verbatim: item.title)
Spacer()
Image(systemName: "battery.75percent")
}
.contentShape(Rectangle())
.onTapGesture(perform: toggle)
if isExpanded {
VStack {
Text("More")
Text("Info")
Text("on item")
}
}
}
} icon: {
Image(systemName: "lightswitch.on.square")
}
}
}
}
#Preview {
ContentView()
}
@helje5
Copy link
Author

helje5 commented Oct 9, 2023

All this seems to work fine w/ a ScrollView, but w/ List, the item being expanded flickers in the animation. The desired behaviour is that the unexpanded thing stays in place and the details slide out.

Looks like the Cell identity should be stable?

Screen.Recording.2023-10-09.at.14.14.56.mov

@helje5
Copy link
Author

helje5 commented Oct 9, 2023

Even with animations disabled this produces flicker. Notice how the top padding of the expanded item seems to increase by a pixel or so (in a production up I've seen multiple pixels).

Screen.Recording.2023-10-09.at.14.17.52.mov

@helje5
Copy link
Author

helje5 commented Oct 9, 2023

Adding an else branch even increases the top-padding:

          if isExpanded {
            VStack {
              Text("More")
              Text("Info")
              Text("on item")
            }
          }
          else {
            Rectangle()
              .fill(.red)
              .frame(maxHeight: 1)
          }

@helje5
Copy link
Author

helje5 commented Oct 9, 2023

Replacing the Label w/ an HStack doesn't help either:

      HStack(alignment: .firstTextBaseline) {
        Image(systemName: "lightswitch.on.square")

        VStack {
          HStack {
            Text(verbatim: item.title)

@helje5
Copy link
Author

helje5 commented Oct 9, 2023

@robb came up w/ hack to workaround the padding issue: https://gist.github.com/robb/b262fe83baabace03cfd65581d1a01bd.

So it seems like if the List Cell contents has a height of larger than 36 pts, there's an extra vertical padding totalling 14 pts being added

This works for me, if I wrap the Label in it:

struct HackTheListLayout: Layout {
  
  let heightMagic : CGFloat = 36 // different by dynamic type?
  let magicFixup  : CGFloat = -2
  
  func sizeThatFits(proposal: ProposedViewSize, 
                    subviews: Subviews, 
                    cache: inout ()) -> CGSize
  {
    if let proposedHeight = proposal.height,
       proposedHeight <= heightMagic
    {
      return subviews.first?.sizeThatFits(proposal)
          ?? proposal.replacingUnspecifiedDimensions()
    } 
    else {
      var size = subviews.first?.sizeThatFits(proposal)
              ?? proposal.replacingUnspecifiedDimensions()
      size.height += magicFixup
      return size
    }
  }
  
  func placeSubviews(in bounds: CGRect,
                     proposal: ProposedViewSize,
                     subviews: Subviews, cache: inout ())
  {}
}

The magic numbers don't always work, seems to depend on the content.

@GeekAndDad
Copy link

Wow this is embarrassing for the SwiftUI team. Quite ugly. I couldn't figure out a way to make it look substantially better :-/

@helje5
Copy link
Author

helje5 commented Oct 16, 2023

I wouldn't call it embarrassing, it seems like a deficiency that should be addressed and it is sad that it still exists. I would even be OK to disable the slide out animation, but the offset issue is really bad.

One thing that can be done to make it work is to put the expanded content into own rows. But that has a lot of other side effects that just don't work well in my particular scenario.

Context: I do have an (4y?) old SwiftUI app in which I had to drop List originally because of such issues. I'm working on an update and thought I'd give List another try. Looks like I have to stick to a plain ScrollView even though I'd love to use the List for the extra features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment