Вопрос или проблема
Я работаю над проектом SwiftUI, где я могу перетаскивать, изменять размер и вращать стикеры. Хотя изменение размера и вращение работает хорошо, у меня возникают проблемы с поведением перетаскивания после вращения стикера.
Проблема:
Когда стикер вращается, перетаскивание его вдоль того, что должно быть «вертикальной» или «горизонтальной» осью, приводит к движению вдоль оригинальных не вращенных осей. Например, если стикер вращается на 90 градусов, его перетаскивание вверх приводит к боковому движению.
Что я пробовал:
- Я использовал rotationEffect для обработки вращения.
- Я корректирую трансляцию, учитывая вращение с помощью тригонометрии, но перетаскивание все еще не ведет себя правильно.
Вот мой код для стикера:
ZStack {
stickyNote.color
.frame(width: stickyNote.size.width, height: stickyNote.size.height)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(isSelected ? Color.blue : Color.clear, lineWidth: 3)
)
.rotationEffect(rotation, anchor: .center)
.offset(x: position.width, y: position.height)
.gesture(
TransformHelper.dragGesture(
position: $position,
lastPosition: $lastPosition,
rotation: rotation,
isResizing: isResizing,
isDragging: $isDragging
)
)
.gesture(
TransformHelper.rotationGesture(
rotation: $rotation,
lastRotation: $lastRotation
)
)
}
А вот код для перемещения и вращения:
static func adjustTranslationForRotation(_ translation: CGSize, rotation: Angle) -> CGSize {
let radians = rotation.radians
let cosTheta = CGFloat(cos(radians))
let sinTheta = CGFloat(sin(radians))
// Корректировка трансляции с учетом вращения
let adjustedX = translation.width * cosTheta - translation.height * sinTheta
let adjustedY = translation.width * sinTheta + translation.height * cosTheta
return CGSize(width: adjustedX, height: adjustedY)
}
static func dragGesture(
position: Binding<CGSize>,
lastPosition: Binding<CGSize>,
rotation: Angle,
isResizing: Bool,
isDragging: Binding<Bool>
) -> some Gesture {
return DragGesture()
.onChanged { value in
if !isResizing {
let adjustedTranslation = adjustTranslationForRotation(value.translation, rotation: rotation)
position.wrappedValue = CGSize(
width: lastPosition.wrappedValue.width + adjustedTranslation.width,
height: lastPosition.wrappedValue.height + adjustedTranslation.height
)
isDragging.wrappedValue = true
}
}
.onEnded { _ in
if isDragging.wrappedValue {
isDragging.wrappedValue = false
lastPosition.wrappedValue = position.wrappedValue
}
}
}
Кто-то сталкивался с аналогичной проблемой, когда направление перетаскивания неправильно после применения вращения? Буду признателен за любые советы о том, как правильно двигать стикер в зависимости от его текущей ориентации!
Заранее спасибо!
Правка: Вот минимальный воспроизводимый пример
import SwiftUI
struct StickyNoteView: View {
@State private var position: CGSize = .zero
@State private var lastPosition: CGSize = .zero
@State private var rotation: Angle = .zero
@State private var lastRotation: Angle = .zero
@State private var isDragging: Bool = false
var body: some View {
ZStack {
Rectangle()
.fill(Color.yellow)
.frame(width: 200, height: 200)
.rotationEffect(rotation)
.offset(x: position.width, y: position.height)
.gesture(
TransformHelper.dragGesture(
position: $position,
lastPosition: $lastPosition,
rotation: rotation,
isResizing: false,
isDragging: $isDragging
)
)
.gesture(
TransformHelper.rotationGesture(
rotation: $rotation,
lastRotation: $lastRotation
)
)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray.opacity(0.3))
.edgesIgnoringSafeArea(.all)
}
}
import SwiftUI
struct TransformHelper {
// Корректировка трансляции с учетом центра стикера
static func adjustTranslationForRotation(_ translation: CGSize, rotation: Angle) -> CGSize {
let radians = rotation.radians
let cosTheta = CGFloat(cos(radians))
let sinTheta = CGFloat(sin(radians))
// Преобразование трансляции в вращенную систему координат
let adjustedX = translation.width * cosTheta - translation.height * sinTheta
let adjustedY = translation.width * sinTheta + translation.height * cosTheta
return CGSize(width: adjustedX, height: adjustedY)
}
// Обработчик жеста перетаскивания
static func dragGesture(
position: Binding<CGSize>,
lastPosition: Binding<CGSize>,
rotation: Angle,
isResizing: Bool,
isDragging: Binding<Bool>
) -> some Gesture {
return DragGesture()
.onChanged { value in
if !isResizing {
let adjustedTranslation = adjustTranslationForRotation(value.translation, rotation: rotation)
position.wrappedValue = CGSize(
width: lastPosition.wrappedValue.width + adjustedTranslation.width,
height: lastPosition.wrappedValue.height + adjustedTranslation.height
)
isDragging.wrappedValue = true
}
}
.onEnded { _ in
if isDragging.wrappedValue {
isDragging.wrappedValue = false
lastPosition.wrappedValue = position.wrappedValue
}
}
}
// Обработчик жеста вращения
static func rotationGesture(
rotation: Binding<Angle>,
lastRotation: Binding<Angle>
) -> some Gesture {
return RotationGesture()
.onChanged { angle in
rotation.wrappedValue = lastRotation.wrappedValue + angle
}
.onEnded { angle in
lastRotation.wrappedValue += angle
}
}
}
Нет необходимости корректировать трансляцию для вращения, просто позвольте модификаторам выполнять работу за вас.
Таким образом, статическая функция adjustTranslationForRotation
не нужна, а функция dragGesture
может быть изменена следующим образом:
// статическая функция dragGesture
if !isResizing {
// let adjustedTranslation = adjustTranslationForRotation(value.translation, rotation: rotation)
position.wrappedValue = CGSize(
width: lastPosition.wrappedValue.width + value.translation.width,
height: lastPosition.wrappedValue.height + value.translation.height
)
isDragging.wrappedValue = true
}
Что я также заметил, так это то, что жесты (особенно вращение) кажутся корректными только когда квадрат находится близко к центру экрана. Однако, возможно, это просто проблема с использованием симулятора.
Ответ или решение
SwiftUI: Исправление проблемы с движением стикеров после их поворота
Ваша проблема связана с неправильным перемещением стикеров в SwiftUI после их поворота. Это распространенная ситуация, где при вращении объекта его координаты меток могут не соответствовать исходным осям, что вызывает путаницу при перетаскивании. Давайте разберем эту проблему более подробно и предложим оптимальное решение.
Описание проблемы
Вы упомянули, что в вашей реализации, после поворота стикера на 90 градусов, движение его вдоль «вертикальных» или «горизонтальных» осей вызывает перемещение вдоль исходных, не вращенных осей. Это происходит из-за необходимости корректировки вектора перемещения после изменения ориентации объекта.
Анализ кода
Ваш код обрабатывает перемещение и вращение с помощью кастомных жестов, что само по себе является хорошей практикой. Однако, в методе adjustTranslationForRotation
и при использовании функции dragGesture
, вы пытаетесь учитывать вращение с помощью тригонометрии, что может привнести ненужную сложность и ошибки в расчетах.
static func adjustTranslationForRotation(_ translation: CGSize, rotation: Angle) -> CGSize {
let radians = rotation.radians
let cosTheta = CGFloat(cos(radians))
let sinTheta = CGFloat(sin(radians))
let adjustedX = translation.width * cosTheta - translation.height * sinTheta
let adjustedY = translation.width * sinTheta + translation.height * cosTheta
return CGSize(width: adjustedX, height: adjustedY)
}
Рекомендации по исправлению
На самом деле, вам не нужно вручную применять коррекцию для перемещения. SwiftUI хорошо справляется с преобразованиями, доступными из самого системного API. Ваша функция dragGesture
может быть упрощена до стандартного перемещения:
static func dragGesture(
position: Binding<CGSize>,
lastPosition: Binding<CGSize>,
isResizing: Bool,
isDragging: Binding<bool>
) -> some Gesture {
return DragGesture()
.onChanged { value in
if !isResizing {
position.wrappedValue = CGSize(
width: lastPosition.wrappedValue.width + value.translation.width,
height: lastPosition.wrappedValue.height + value.translation.height
)
isDragging.wrappedValue = true
}
}
.onEnded { _ in
if isDragging.wrappedValue {
isDragging.wrappedValue = false
lastPosition.wrappedValue = position.wrappedValue
}
}
}
Таким образом, вы избегаете необходимости вручную пересчитывать положение стикера после его вращения. SwiftUI автоматически применит необходимые трансформации, и ваше приложение будет работать, как ожидается.
Дополнительные замечания
-
Проблемы с жестами в симуляторе – как вы сами отметили, наблюдение за работой жестов может давать разные результаты в симуляторе и на реальном устройстве. Регулярно тестируйте на реальном оборудовании.
-
Обеспечение отзывчивого интерфейса – убедитесь, что размер и положение стикеров правильно обновляются в зависимости от размера экрана и ориентации, чтобы пользователям было удобно взаимодействовать с элементами интерфейса.
-
Тестирование на разных устройствах – после исправления кода обязательно протестируйте работу на устройствах с различными размерами экранов и версиями iOS.
Подводя итог, правильные исправления и упрощения кода помогут устранить проблему с перемещением стикеров после их вращения. SwiftUI предоставляет мощные механизмы для управления жестами и преобразованиями, которые при правильном использовании значительно упрощают разработку и обеспечивают ожидаемое поведение интерфейсов.