Я пытался несколько дней, но не смог найти решения. Моя цель — переместить изображение из одного места в другое. Но я не хочу, чтобы при перемещении изображение перемещалось только вверх и вниз. Я хочу, чтобы это выглядело так, будто изображение скользит, пока скользит страница. Извините, если я не объяснил это ясно. Когда вы запустите код на своем компьютере, вы поймете анимацию, которую я пытаюсь создать. прошу вашей помощи
struct SwiftUIView: View {
@State private var showFirstView = true
@Namespace var namespace
var body: some View {
GeometryReader { geometry in
ZStack {
if showFirstView {
FirstView()
.transition(.move(edge: .trailing))
.frame(width: geometry.size.width, height: geometry.size.height)
} else {
SecondView()
.transition(.move(edge: .leading))
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
.animation(.easeInOut(duration: 0.5), value: showFirstView)
}
.overlay(alignment: .bottom) {
Button(action: {
withAnimation {
showFirstView.toggle()
}
}) {
Text("Switch View")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.padding()
}
}
func FirstView() -> some View {
ZStack{
Color.red
.ignoresSafeArea()
cusview()
}
}
func SecondView() -> some View {
ZStack {
Color.green
.ignoresSafeArea()
cusview()
.offset(y: -150)
}
}
func cusview() -> some View {
Image(.image10)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 140, height: 100)
.matchedGeometryEffect(id: "one", in: namespace)
}
}
#Preview {
SwiftUIView()
}
Я понятия не имею, почему вы так структурировали свой код или зачем нужна программа чтения геометрии, но это само по себе может быть целым обсуждением.
Проблема, с которой вы столкнулись, связана с тем, что вы применяете переход перемещения ко всему представлению, содержащему изображение. Если вы закомментируете соответствующие переходы для FirstView и SecondView, вы должны увидеть анимацию изображения правильно (кстати, .image10
существует только на вашей стороне, вы можете обновить код, который вы предоставили выше, на что-то более общее, например, на символ/ икона).
if showFirstView {
FirstView()
// .transition(.move(edge: .trailing))
.frame(width: geometry.size.width, height: geometry.size.height)
} else {
SecondView()
// .transition(.move(edge: .leading))
.frame(width: geometry.size.width, height: geometry.size.height)
}
Если вам абсолютно необходима анимация скольжения/перемещения, используйте вместо этого асимметричный переход:
if showFirstView {
FirstView()
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .opacity))
.frame(width: geometry.size.width, height: geometry.size.height)
} else {
SecondView()
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .opacity))
.frame(width: geometry.size.width, height: geometry.size.height)
}
Это придаст переходу белый цвет, но я не знаю, как этого избежать. В качестве обходного пути вы можете установить цвет фона для ZStack, который будет либо третьим цветом, либо одним из двух цветов (красным или зеленым):
ZStack {
Color.blue // <-- Here, set to this to either red or green, or some other colour
.ignoresSafeArea()
if showFirstView {
FirstView()
...
Кстати, ZStack по умолчанию расширяется до размера контейнера, поэтому GeometryReader не требует установки размеров кадра. Кроме того, зачем использовать наложение для размещения кнопки, если у вас уже есть ZStack? Ниже показано, как это может быть намного чище:
var body: some View {
ZStack {
if showFirstView {
FirstView()
} else {
SecondView()
}
Button("Switch View"){
showFirstView.toggle()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.frame(maxHeight: .infinity, alignment: .bottom)
}
.animation(.easeInOut(duration: 0.5), value: showFirstView)
}
Когда я попробовал ваш код в симуляторе (iPhone 15 под управлением iOS 17.5), анимация изображения отключилась:
Вы сказали в своем посте:
Я не хочу, чтобы изображение перемещалось только вверх и вниз.
поэтому я спросил в комментарии, как именно вы хотите, чтобы он двигался? Я бы предположил, что существует два возможных способа анимировать изображение, если движение не должно быть только вертикальным:
Изображение в появляющемся виде может оставаться таким, какое оно есть (перемещение по диагонали), но изображение в исчезающем виде также может перемещаться по диагонали, а не только по вертикали.
Это будет означать, что когда одно изображение перемещается вправо, новое изображение появляется слева на той же высоте. Это по-прежнему будет выглядеть бессвязным, но, по крайней мере, изображение будет иметь постоянное направление движения.
Я пытался найти способ реализовать это таким образом, но, извините, безуспешно.
Анимация была бы менее бессвязной, если бы изображение плавно перемещалось из одной позиции в другую. Если вы не хотите, чтобы анимация была только вертикальной, тогда изображение должно будет двигаться по какой-то кривой.
Этот второй стиль анимации можно реализовать, отобразив изображение как еще один слой в ZStack
и заставив его перемещаться между позициями заполнителей в двух дочерних представлениях.
Заполнители используют matchedGeometryPostion
с isSource: true
(что на самом деле является значением по умолчанию), поэтому они определяют позиции, между которыми перемещаются.
Само изображение использует matchedGeometryPosition
с isSource: false
, чтобы оно соответствовало исходным позициям.
По умолчанию matchedGeometryEffect
устанавливает размер изображения из активного заполнителя, поэтому нет необходимости устанавливать отдельный frame
для изображения. Однако лучше всего, если изображение будет отображаться поверх ZStack
, а не как обычный слой. Это гарантирует, что изображение в его естественном размере не приведет к раздуванию размера ZStack
в случае, когда размер изображения больше размера экрана.
Изогнутую траекторию можно получить, используя разную длительность анимации для переходов и matchedGeometryEffect
:
matchedGeometryEffect
быстрее перехода, изображение следует по изогнутой траектории со стороны исчезающего видаmatchedGeometryEffect
медленнее, чем переход, изображение следует по изогнутой траектории со стороны появляющегося вида.Обновленный пример ниже показывает, как это работает. Другие изменения и предложения:
Два дочерних представления уже занимают все доступное пространство, поскольку оба содержат жадный символ Color
. Это означает, что нет необходимости устанавливать frame
для этих представлений, поэтому GeometryReader
не нужен.
Знак GeometryReader
не понадобится, даже если дочерние представления не содержат Color
. Если вы хотите увеличить изображение до максимального размера, установите frame
с помощью maxWidth: .infinity, maxHeight: .infinity
.
Если у вас есть модификатор .animation
, флаг переключать withAnimation
не нужно. Фактически, использование отдельных модификаторов .animation
вместо withAnimation
является ключом к возможности контролировать перемещение изображения.
Модификатор .scaledToFill
— это более простой способ применения .aspectRatio(contentMode: .fill)
.
Модификатор .foregroundColor
устарел, вместо него используйте .foregroundStyle
.
Модификатор .cornerRadius
также устарел, вместо него используйте .clipShape
или просто покажите RoundedRectangle
на заднем плане.
Стиль кнопки .borderedProminent
на самом деле является более простым способом добиться того же стиля кнопки. Чтобы он выглядел точно так же, как раньше, вам просто нужно добавить к метке немного отступов.
И действие, и метка кнопки могут быть представлены как замыкающие замыкания , что позволяет избежать набора круглых скобок. Сделать это таким образом означает пометить метку для метки, а не метку для действия.
Кнопка отображается в виде наложения, чтобы расположить ее внизу ZStack
. Это нормально, но вы также можете рассмотреть возможность создания еще одного слоя ZStack
и позиционирования, установив maxHeight: .infinity
с помощью alignment: .bottom
. Результат тот же, так что все зависит только от личных предпочтений.
struct SwiftUIView: View {
@State private var showFirstView = true
@Namespace var namespace
var body: some View {
ZStack {
if showFirstView {
FirstView()
.transition(.move(edge: .trailing))
} else {
SecondView()
.transition(.move(edge: .leading))
}
}
.animation(.easeInOut(duration: 0.8), value: showFirstView)
.overlay {
cusview()
// Try adjusting the duration to get different movements
.animation(.easeInOut(duration: 0.7), value: showFirstView)
}
.overlay(alignment: .bottom) {
Button {
showFirstView.toggle()
} label: {
Text("Switch View")
.padding(.horizontal, 4)
.padding(.vertical, 9)
}
.buttonStyle(.borderedProminent)
.padding()
}
}
func FirstView() -> some View {
ZStack{
Color.red
.ignoresSafeArea()
imagePlaceholder
}
}
func SecondView() -> some View {
ZStack {
Color.green
.ignoresSafeArea()
imagePlaceholder
.offset(y: -150)
}
}
private var imagePlaceholder: some View {
Color.clear
.frame(width: 140, height: 100)
.matchedGeometryEffect(id: "one", in: namespace, isSource: true)
}
func cusview() -> some View {
Image(.image10)
.resizable()
.scaledToFill()
.matchedGeometryEffect(id: "one", in: namespace, isSource: false)
}
}
@burak Всегда пожалуйста. Если это не та анимация, которую вы искали, возможно, вы могли бы объяснить, что вам на самом деле нужно? В противном случае, если ответ помог, возможно, вы могли бы принять его, нажав галочку? ;)
Не могли бы вы уточнить, как именно вы хотите, чтобы изображение двигалось, если оно не должно просто скользить вверх и вниз? Допустим, мы находимся на первом (красном) виде. Когда мы переходим ко второму (зеленому) виду, первый вид сдвигается вправо, а второй вид выдвигается слева. Как должно двигаться изображение? Должен ли он также соскользнуть вправо? И что потом?