1

I have a List partially covered by a translucent view (let's call it the overlay). My problem is that for long lists last rows are not accessible since they are covered by the overlay.

I'm using a ZStack to layout the final view. I've thought about adding some kind of padding on last rows that could make the list content a bit larger, so it can scroll completely out of the overlay, but I don't know how, or even if using ZStack is the correct way to doing for lists.

what I get

import SwiftUI

struct ListWithBottomOverlay: View {
  var body: some View {
    GeometryReader { proxy in
      ZStack {
        List {
          ForEach(1..<20) { number in
            Text("\(number)")
          }
        }

        VStack {
          Spacer().frame(maxHeight: .infinity)

          VStack {
            HStack {
              Button(action: {}, label: { Text("Hello") })
                .frame(minHeight: 100)
            }
            HStack {
              Button(action: {}, label: { Text("World!") })
                .frame(minHeight: 100)
            }
          }
          .frame(maxWidth: .infinity)
          .background(Color(.yellow).opacity(0.8))
        }
      }
    }
  }
}

struct ListWithBottomOverlay_Previews: PreviewProvider {
  static var previews: some View {
    ListWithBottomOverlay()
  }
}

I apologize if it is a duplicate question, I've just started to learn SwiftUI so I'm a bit lost yet about how to search for correct terms.

cbuchart
  • 10,847
  • 9
  • 53
  • 93

2 Answers2

1

The possible solution is to calculate height of overlay area and add some transparent view with that height to the bottom of the list.

Here is a demo of approach using view preferences. Tested with Xcode 12 / iOS 14

demo

struct ListWithBottomOverlay: View {
  @State private var height = CGFloat.zero
  var body: some View {
    GeometryReader { proxy in
      ZStack {
        List {
          ForEach(1..<20) { number in
            Text("\(number)")
          }
          Color.clear.frame(height: height)  // injected empty space
        }

        VStack {
          Spacer().frame(maxHeight: .infinity)

          VStack {
            HStack {
              Button(action: {}, label: { Text("Hello") })
                .frame(minHeight: 100)
            }
            HStack {
              Button(action: {}, label: { Text("World!") })
                .frame(minHeight: 100)
            }
          }
          .frame(maxWidth: .infinity)
          .background(GeometryReader {
                // use color filled area in background to read covered frame
                Color(.yellow).opacity(0.8)
                    .edgesIgnoringSafeArea(.bottom)
                    .preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
            })
        }
      }
      .onPreferenceChange(ViewHeightKey.self) {
        // view preferences transferred in one rendering cycle and
        // give possibility to update state
        self.height = $0
      }
    }
  }
}

struct ViewHeightKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value += nextValue()
    }
}

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thanks! That's just what I was looking for... still looks a bit weird for me the use of the PreferenceKey and things like that, but I supposed that's the SwiftUI's way ;) – cbuchart Aug 23 '20 at 21:48
  • View preferences (ie PreferenceKey) is a SwiftUI way to pass information from Child view to Parent view, (EnvironmentKey is for opposite direction). – Asperi Aug 24 '20 at 05:48
0

Now you can add to list .safeAreaInset

List {
    // content
}
.safeAreaInset(.bottom) {
    // view for bottom
}
Serg Basin
  • 31
  • 4