Implementei uma linha do tempo de edição de vídeo de exemplo usando SwiftUI e estou enfrentando problemas. Então, estou dividindo o problema em partes e postando cada problema como uma pergunta separada. No código abaixo, tenho uma linha do tempo simples usando um HStack
espaçador esquerdo, espaçador direito (representado como cor preta simples) e uma IU de aparador no meio. O aparador é redimensionado conforme as alças esquerda e direita são arrastadas. Os espaçadores esquerdo e direito também se ajustam em largura conforme as alças do aparador são arrastadas.
Problema : Quero manter as miniaturas de fundo (implementadas atualmente como retângulos simples preenchidos em cores diferentes) no aparador estacionárias enquanto o aparador é redimensionado. Atualmente, elas se movem conforme o aparador é redimensionado, como visto no gif abaixo. Como faço para consertar isso?
import SwiftUI
struct SampleTimeline: View {
let viewWidth:CGFloat = 340 //Width of HStack container for Timeline
@State var frameWidth:CGFloat = 280 //Width of trimmer
var minWidth: CGFloat {
2*chevronWidth + 10
} //min Width of trimmer
@State private var leftViewWidth:CGFloat = 20
@State private var rightViewWidth:CGFloat = 20
var chevronWidth:CGFloat {
return 24
}
var body: some View {
HStack(spacing:0) {
Color.black
.frame(width: leftViewWidth)
.frame(height: 70)
HStack(spacing: 0) {
Image(systemName: "chevron.compact.left")
.frame(width: chevronWidth, height: 70)
.background(Color.blue)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged({ value in
leftViewWidth = max(leftViewWidth + value.translation.width, 0)
if leftViewWidth > viewWidth - minWidth - rightViewWidth {
leftViewWidth = viewWidth - minWidth - rightViewWidth
}
frameWidth = max(viewWidth - leftViewWidth - rightViewWidth, minWidth)
})
.onEnded { value in
}
)
Spacer()
Image(systemName: "chevron.compact.right")
.frame(width: chevronWidth, height: 70)
.background(Color.blue)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged({ value in
rightViewWidth = max(rightViewWidth - value.translation.width, 0)
if rightViewWidth > viewWidth - minWidth - leftViewWidth {
rightViewWidth = viewWidth - minWidth - leftViewWidth
}
frameWidth = max(viewWidth - leftViewWidth - rightViewWidth, minWidth)
})
.onEnded { value in
}
)
}
.foregroundColor(.black)
.font(.title3.weight(.semibold))
.background {
HStack(spacing:0) {
Rectangle().fill(Color.red)
.frame(width: 70, height: 60)
Rectangle().fill(Color.cyan)
.frame(width: 70, height: 60)
Rectangle().fill(Color.orange)
.frame(width: 70, height: 60)
Rectangle().fill(Color.brown)
.frame(width: 70, height: 60)
Rectangle().fill(Color.purple)
.frame(width: 70, height: 60)
}
}
.frame(width: frameWidth)
.clipped()
Color.black
.frame(width: rightViewWidth)
.frame(height: 70)
}
.frame(width: viewWidth, alignment: .leading)
}
}
#Preview {
SampleTimeline()
}
Atualização : consegui resolver o problema da seguinte forma, mas ainda sinto que é meio que uma solução alternativa (pois configurei um deslocamento para a visualização de miniaturas). Por favor, poste uma solução melhor e mais precisa se você acha que há alguma (como usar uma máscara que também reduz a largura do quadro do aparador ao mesmo tempo).
import SwiftUI
struct SampleTimeline: View {
let viewWidth:CGFloat = 340 //Width of HStack container for Timeline
@State var frameWidth:CGFloat = 280 //Width of trimmer
var minWidth: CGFloat {
2*chevronWidth + 10
} //min Width of trimmer
@State private var leftViewWidth:CGFloat = 20
@State private var rightViewWidth:CGFloat = 20
@GestureState private var leftEndPanned = false
@GestureState private var rightEndPanned = false
var chevronWidth:CGFloat {
return 24
}
var body: some View {
HStack(spacing:0) {
Color.clear
.frame(width: leftViewWidth)
.frame(height: 70)
HStack(spacing: 0) {
Image(systemName: "chevron.compact.left")
.frame(width: chevronWidth, height: 70)
.background(Color.blue)
.gesture(
DragGesture(minimumDistance: 0)
.updating($leftEndPanned, body: { _, state, _ in
state = true
})
.onChanged({ value in
leftViewWidth = max(leftViewWidth + value.translation.width, 0)
if leftViewWidth > viewWidth - minWidth - rightViewWidth {
leftViewWidth = viewWidth - minWidth - rightViewWidth
}
frameWidth = max(viewWidth - leftViewWidth - rightViewWidth, minWidth)
})
.onEnded { value in
}
)
Spacer()
Image(systemName: "chevron.compact.right")
.frame(width: chevronWidth, height: 70)
.background(Color.blue)
.gesture(
DragGesture(minimumDistance: 0)
.updating($rightEndPanned, body: { _, state, _ in
state = true
})
.onChanged({ value in
rightViewWidth = max(rightViewWidth - value.translation.width, 0)
if rightViewWidth > viewWidth - minWidth - leftViewWidth {
rightViewWidth = viewWidth - minWidth - leftViewWidth
}
frameWidth = max(viewWidth - leftViewWidth - rightViewWidth, minWidth)
})
.onEnded { value in
}
)
}
.foregroundColor(.black)
.font(.title3.weight(.semibold))
.background {
HStack(spacing:0) {
Rectangle().fill(Color.red)
.frame(width: 70, height: 60)
Rectangle().fill(Color.cyan)
.frame(width: 70, height: 60)
Rectangle().fill(Color.orange)
.frame(width: 70, height: 60)
Rectangle().fill(Color.brown)
.frame(width: 70, height: 60)
Rectangle().fill(Color.purple)
.frame(width: 70, height: 60)
}
.frame(width: viewWidth - leftViewWidth - rightViewWidth, alignment: .leading)
.offset(x: -leftViewWidth)
.background(Color.yellow)
.clipped()
}
Color.clear
.frame(width: rightViewWidth)
.frame(height: 70)
}
.frame(width: viewWidth, alignment: .leading)
}
}
#Preview {
SampleTimeline()
}
Atualmente, as cores são mostradas no fundo do aninhado
HStack
. No entanto, issoHStack
está se movendo, dependendo do tamanho deleftViewWidth
. Então, embora você esteja definindo um tamanho de quadro menor no fundo, você não está compensando a posição da pilha.Eu sugeriria usar uma abordagem diferente. Em vez de tentar recortar o fundo, deixe-o inalterado e aplique um
.mask
para controlar qual parte dele deve ficar visível.Você aceitou anteriormente outra resposta para arrastar os aparadores que usavam
GestureState
variáveis para rastrear a posição de arrasto (era minha resposta). O exemplo na pergunta está fazendo isso de uma maneira diferente. A versão abaixo é baseada na mesma solução que eu forneci antes. Eu sugeriria que esta é uma maneira mais simples de fazer isso.