Я хотел бы обрезать представление (изображение) в круг, когда представление перетаскивается вниз, чтобы его закрыть (как в видео ниже). В моем нынешнем подходе есть некоторые ошибки, поскольку перетаскивание представления при изменении рамки очень ошибочно. Как лучше всего воспроизвести клип/переход в видео?
https://drive.google.com/file/d/1CUzGdsbO4Nx4qQgdonVWMzA_Na0VmwCS/view?usp=sharing
import SwiftUI
struct uyjrbertbr: View {
@State var offset: CGSize = .zero
@State var cornerRad: CGFloat = 0.0
@State var widthStory = 0.0
@State var heightStory = 0.0
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: cornerRad)
.foregroundStyle(.blue)
.ignoresSafeArea()
.frame(width: widthStory == 0.0 ? widthOrHeight(width: true) : widthStory)
.frame(height: heightStory == 0.0 ? widthOrHeight(width: false) : heightStory)
.offset(x: offset.width, y: offset.height)
.gesture(DragGesture()
.onChanged({ value in
if value.translation.height > 0 {
self.offset = value.translation
clip()
}
})
.onEnded({ value in
withAnimation(.easeInOut(duration: 0.2)) {
offset = .zero
cornerRad = 0.0
widthStory = widthOrHeight(width: true)
heightStory = widthOrHeight(width: false)
}
})
)
}
.onAppear(perform: {
widthStory = widthOrHeight(width: true)
heightStory = widthOrHeight(width: false)
})
}
func clip() {
var ratio = abs(self.offset.height) / 200.0
ratio = min(1.0, ratio)
self.cornerRad = ratio * 300.0
let min = 80.0
let maxWidth = widthOrHeight(width: true)
let maxHeight = widthOrHeight(width: false)
widthStory = maxWidth - ((maxWidth - min) * ratio)
heightStory = maxHeight - ((maxHeight - min) * ratio)
}
}
#Preview {
uyjrbertbr()
}
func widthOrHeight(width: Bool) -> CGFloat {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
if width {
return window?.screen.bounds.width ?? 0
} else {
return window?.screen.bounds.height ?? 0
}
}
Обычный способ обрезать представление — применить .clipShape
. Однако в качестве аргумента используется Shape
, поэтому немного сложно сделать его условным, а также контролировать его размер.
Еще один способ сделать это — применить маску. Это может принимать ViewBuilder
в качестве аргумента, поэтому мы можем сделать его зависящим от того, когда происходит перетаскивание, и можно легко установить размер кадра.
Затем:
Rectangle
в полный размер экрана..position
в положение начала перетаскивания. Чтобы это работало, выравнивание маски должно быть .topLeading
, а координатное пространство .local
должно использоваться для жеста перетаскивания..mask
перед модификатором .offset
.GeometryReader
.DragGesture
, оно автоматически сбрасывается в конце жеста. Это позволяет избежать необходимости его сброса в .onEnded
.struct ContentView: View {
let loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
@GestureState private var offset: CGSize = .zero
@State private var startOfDragLocation: CGPoint = .zero
@State private var startOfDragTime: Date = .now
@State private var circleSize: CGFloat = .zero
var body: some View {
GeometryReader { proxy in
let h = proxy.size.height
Text(loremIpsum)
.font(.title2)
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background {
Color.blue
.padding(-h)
}
.mask(alignment: .topLeading) {
if offset == .zero {
Rectangle().ignoresSafeArea()
} else {
Circle()
.position(startOfDragLocation)
.frame(width: circleSize, height: circleSize)
}
}
.offset(offset)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged { value in
if startOfDragLocation != value.startLocation {
startOfDragLocation = value.startLocation
startOfDragTime = .now
}
circleSize = max(100, h + (h * startOfDragTime.timeIntervalSinceNow / 0.5))
}
.updating($offset) { value, state, trans in
state = value.translation
}
)
}
}
}
Идеальный! Не могли бы вы заглянуть: stackoverflow.com/questions/78745029/…