Swift UI — анимация отдельных представлений в сетке

#button #view #swiftui #action

#кнопка #Вид #swiftui #Экшен

Вопрос:

Я пытаюсь решить следующую проблему.

У меня есть представление сетки, которое генерирует массив представлений. Смотрите ниже:

 struct GridStack<Content: View> : View {
    let rows :Int
    let columns : Int
    let content : (Int, Int) -> Content
    
    var body: some View {
        VStack{
            ForEach(0 ..< rows){ row in
                HStack{
                    ForEach(0 ..< columns){ column in
                        self.content(row, column)
                    }
                }
            }
        }
    }
}
 

В настоящее время у меня есть этот рабочий код, который отправляет значение массива в целочисленную переменную ‘upTo’ в моем основном коде:

 GridStack(rows: 3, columns: 4){ rows, cols in
          Button(action: {
                 upTo = rows * 4   cols   1
          }, label: {
                 Text("Number (rows * 4   cols   1)")
          })
}
 

Я хотел бы сделать этот текст анимированным, однако, если я добавлю анимацию непосредственно к тексту, каждый из текстов перемещается при нажатии там, где я хотел бы анимировать только тот, который я нажимаю.

Я попытался превратить текст в вид кнопки и передать его в метку главной кнопки, но, по-видимому, встраивание кнопок является преступлением, наказуемым смертной казнью.

У кого-нибудь есть идеи? Заранее спасибо!

Комментарии:

1. Вы пытаетесь поместить кнопку в кнопку — в SwiftUI, который реагирует, работает только один, который реагирует первым. Зачем это нужно делать?

2. Вы вкладываете кнопки? Это не очень хорошая идея, поскольку система не будет знать, какую кнопку активировать. Кроме того, вы создаете два экземпляра своего test представления в Cententview .

3. Нет ли способа сообщить системе активировать обе кнопки?

4. Для меня это скорее похоже на проблему XY . Как сказал другой, и вы испытали, вложенные кнопки — не очень хорошая идея. Не было бы более подходящим поместить анимацию в модификатор onTapGesture представления?

5. Вот хороший обновленный вопрос для всех вас, надеюсь, он внесет больше ясности

Ответ №1:

Я думаю, что возможны два решения. Пожалуйста, посмотрите мои рабочие примеры ниже.

Вы бы хотели, чтобы примененный эффект сохранялся после нажатия кнопки? В этом случае я бы рекомендовал создать пользовательское представление, в котором хранится состояние.

 struct GridStack<Content: View> : View {
    let rows :Int
    let columns : Int
    let content : (Int, Int) -> Content
    
    var body: some View {
        VStack{
            ForEach(0 ..< rows){ row in
                HStack{
                    ForEach(0 ..< columns){ column in
                        self.content(row, column)
                    }
                }
            }
        }
    }
}

struct MyCustomView: View {
    let upTo: Int
    let callback: () -> Void
    
    @State var animate = false
    
    var body: some View {
        Button(action: {
            self.callback()
            self.animate.toggle()
        }) {
            Text("Number (self.upTo)")
                .background(self.animate ? Color.red : Color.white)
                .animation(.linear(duration: 1.0))
        }
    }
}

struct ContentView2: View {
    @State var upTo: Int = 0
    
    var body: some View {
        VStack {
            Text("(self.upTo)")

            GridStack(rows: 3, columns: 4) { rows, cols in
                MyCustomView(upTo: rows * 4   cols   1, callback: {
                    self.upTo = rows * 4   cols   1
                })
            }
        }
    }
}

struct ContentView2_Previews: PreviewProvider {
    static var previews: some View {
        ContentView2()
    }
}
 

В противном случае подойдет пользовательский стиль кнопки. В этом случае вы можете получить доступ isPressed к свойству конфигурации.

 struct GridStack<Content: View> : View {
    let rows :Int
    let columns : Int
    let content : (Int, Int) -> Content
    
    var body: some View {
        VStack{
            ForEach(0 ..< rows){ row in
                HStack{
                    ForEach(0 ..< columns){ column in
                        self.content(row, column)
                    }
                }
            }
        }
    }
}

struct MyCustomButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .background(configuration.isPressed ? Color.red : Color.white)
            .animation(.linear(duration: 1.0))
    }
}

struct ContentView2: View {
    @State var upTo: Int = 0
    
    var body: some View {
        VStack {
            Text("(self.upTo)")

            GridStack(rows: 3, columns: 4) { rows, cols in
                Button(action: {
                    self.upTo = rows * 4   cols   1
                }) {
                    Text("Number (rows * 4   cols   1)")
                }
                .buttonStyle(MyCustomButtonStyle())
            }
        }
    }
}

struct ContentView2_Previews: PreviewProvider {
    static var previews: some View {
        ContentView2()
    }
}
 

Комментарии:

1. Потрясающе! Еще раз спасибо, Николай. Я использовал первый пример, поскольку второй мне немного непонятен. Честно говоря, я не совсем понимаю, какую роль играет переменная обратного вызова? Это переменная? И это то, что позволяет ContentView2 считывать значение, выбранное при нажатии кнопки?

2. Добро пожаловать! Кнопка помещается внутрь MyCustomView представления. Ваша цель — выполнить действие, нажав на кнопку. Но на самом деле, что целочисленное состояние upTo недоступно изнутри MyCustomView , передается замыкание, которое «описывает», что делать ContentView2 . И, наконец, это закрытие вызывается закрытием действия кнопки. Подробнее о замыканиях читайте здесь .

3. Хорошо, я буду практиковать свои замыкания! Это немного отчаянно, но не могли бы вы взглянуть на мой другой вопрос в моем профиле относительно поэлементного умножения некоторых массивов? Что-то в моем вычислении массивов превращает их в любой операнд, что означает, что я не могу умножать элементы массива.