Как создать пользовательскую привязку SwiftUI с другим типом для привязки и для отображения?

Согласно этой статье, должно быть просто создать пользовательскую привязку с разными типами для привязки и отображения. Это мой нерабочий пример:

import SwiftUI

struct BindingButton: View {
    @Binding var title: String
    var onTap: () -> Void
    var body: some View {
        Button {
            onTap()
        } label: {
            Text(title)
        }
    }
}

struct CustomBinding: View {
    @State var date: Date
    @State var int: Int
    
    lazy var descriptiveDate = Binding(
        get: { //Escaping closure captures mutating 'self' parameter
            return self.date.description
        },
        set: { _ in }
    )
    
    lazy var descriptiveInt = Binding(
        get: { //Escaping closure captures mutating 'self' parameter
            return String(self.int)
        },
        set: { _ in }
    )
    
    var body: some View {
        VStack {
            // Cannot find '$descriptiveDate' in scope
            BindingButton(title: $descriptiveDate) {
                date = Date()
            }
            // Cannot find '$descriptiveInt' in scope
            BindingButton(title: $descriptiveInt) {
                int += 2
            }
        }
    }
}

Я хотел бы связать Date и Int, но использовать строку для отображения. Возможно ли это без двойной привязки? Я имею в виду, что я хотел бы избежать создания первой привязки для Date и Int, а затем .onChange обновить другую привязку для String и String. Должен ли я делать это так, или, может быть, есть более элегантный способ?

Зачем вам нужна привязка к заголовку кнопки? Привязка необходима только тогда, когда вам нужно изменить состояние дочернего компонента. Ваш BindingButton ничего в нем не меняет.

Rico Crescenzio 30.03.2023 17:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
99
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Binding по определению является двусторонним соединением. lazy подразумевает, что код запускается только один раз. Исходя из этого и пустого set ваши descriptiveDate и descriptiveInt не должны быть Binding просто get для String

struct CustomBinding: View {
    @State var date: Date
    @State var int: Int
    
    var descriptiveDate: String {
        date.description
    }
            
    var descriptiveInt : String{
        String(self.int)
    }
    
    var body: some View {
        VStack {
            BindingButton(title: descriptiveDate) {
                date = Date()
            }
            BindingButton(title: descriptiveInt) {
                int += 2
            }
        }
    }
}

struct BindingButton: View {
    let title: String
    var onTap: () -> Void
    var body: some View {
        Button {
            onTap()
        } label: {
            Text(title)
        }
    }
}

lazy не работает с SwiftUIViews, когда @State вызывает перерисовку, вам нужно пересчитать body и описательные переменные.

Спасибо за ваши усилия ;) Еще один вопрос. Когда я изменю дату с Button при нажатии, изменится ли название этой кнопки так же, как это? Что приведет к обновлению этого заголовка?

Bartłomiej Semańczyk 30.03.2023 20:52

@BartłomiejSemańczyk немедленно, установка даты вызовет перерисовку

lorem ipsum 30.03.2023 20:54

Хорошо, так что для меня это лучшее решение, я думаю.

Bartłomiej Semańczyk 30.03.2023 20:56

Полностью согласен с предыдущим ответом, что в данном случае привязка не нужна. Но чтобы ответить на другую часть вопроса: «Возможно ли иметь взаимозависимые @State и @Binding без двойной привязки?»

Ну, во-первых, даже самым примитивным способом не нужна кастомная привязка. Вместо этого вы можете использовать 2 переменные состояния:

    @State var descriptiveDate: String
    @State var descriptiveInt: String

и используйте либо didSet:

    @State var date: Date {
        didSet {
            descriptiveDate = date.description
        }
    }
    @State var int: Int {
        didSet {
            descriptiveInt = String(int)
        }
    }
    
    @State var descriptiveDate: String
    @State var descriptiveInt: String

Или реализовать onChange для date и int состояния:

struct CustomBinding: View {
    
    @State var date: Date
    @State var int: Int
    @State var descriptiveDate: String
    @State var descriptiveInt: String
    
    var body: some View {
        VStack {
            BindingButton(title: $descriptiveDate) {
                date = Date()
            }
            .onChange(of: date) { newDate in
                descriptiveDate = newDate.description
            }
            BindingButton(title: $descriptiveInt) {
                int += 2
            }
            .onChange(of: int) { newInt in
                descriptiveInt = String(int)
            }
        }
        
    }
}

Но это все еще немного избыточно, так как у вас будет дублирование @State для каждой переменной. Поэтому, чтобы избежать этой пользовательской привязки, полезно:

            BindingButton(title: Binding(
                get: { int.description },
                set: { int = Int($0)! }
            )) {
                int += 2
            }

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

Так что мы избежали "двойной" переменной - не совсем. Поскольку у вас есть 2 части информации, которые могут меняться по некоторым правилам, но независимо друг от друга, вам нужно 2 чего-то. Но пользовательская привязка — это только одна из возможностей.

О, это довольно красиво, я не знал, что могу добавить didSet к свойству @State.

Bartłomiej Semańczyk 30.03.2023 20:50

Другие вопросы по теме