iosswiftuihstack

SwiftUI HStack clipping subviews


I am trying to implement a video scrubber/trimmer in SwiftUI. To do this, put image thumbnails in an HStack as follows:

struct ThumbnailsView: View {
  @State private var thumbImages:[ThumbImage] = []

   var body: some View {
      HStack(alignment: .center, spacing: 0) {
          ForEach(thumbImages, id: \.time) { thumbImage in
              Image(uiImage: thumbImage.image)
                  .border(Color.white, width: 5)
           }
           .padding(5)
       }
       
    }
 }

 struct ThumbImage {
   var time: CMTime
   let image: UIImage
 }

This should basically allow to construct series of images in the scrubber/trimmer (like in Photos app). However, my requirement (which is different from behavior in Photos app) is that as user trims from either end, the trimmer gets shortened and clips the leftmost(or rightmost depending on direction of trim) thumbnail by respective amount, so that only a fraction of the leftmost or rightmost thumbnail is shown as the user drags the end of the trimmer. I could do it in UIKit but just want to understand how this can be done in SwiftUI.

enter image description here


Solution

  • Here is a quick demo for what I think you are looking for. It's gonna get a bit more complicated when inside a ScollView, but you have a starting point.

    enter image description here

    struct ContentView: View {
        
        @State private var timecode = 200.0
        @State private var dragAmount = 0.0
        
        var body: some View {
            VStack(alignment: .leading) {
                Image("thumbnail")
                    .resizable()
                    .scaledToFit()
                
                Spacer()
                
                HStack(spacing: 0) {
                    ForEach(0..<3) { _ in
                        Image("thumbnail")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 120) // this one is important
                    }
                    
                }
                .border(.blue)
                .frame(width: timecode + dragAmount, alignment: .leading)
                .clipped()
                
                .overlay(alignment: .leading) {
                    Rectangle()
                        .fill(.yellow)
                        .frame(width: 10, alignment: .leading)
                        .offset(x: timecode + dragAmount)
                    
                        .gesture(DragGesture()
                            .onChanged { value in
                                dragAmount = value.translation.width
                            }
                            .onEnded { value in
                                timecode += value.translation.width
                                dragAmount = 0
                            }
                                 , including: .gesture)
                }
                Spacer()
                Text("timecode = \(timecode, specifier: "%.1f")")
                    .frame(maxWidth: .infinity, alignment: .center)
            }
        }
    }