В настоящее время я изучаю SwiftUI и хочу разработать собственное приложение. Я разработал LoginView и LoginHandler, которые должны позаботиться обо всей логике входа в систему. Когда пользователь вводит неправильное имя пользователя / пароль, на экране должно появиться предупреждение. Я решил это с помощью переменной состояния loginError. Но теперь наступает сложная часть, так как я хочу передать привязку этой переменной к моей функции входа в систему в LoginHandler. Взгляните на следующий код:


import SwiftUI

struct LoginView: View
{
    @EnvironmentObject var loginHandler: LoginHandler
    
    @State private var username: String = ""
    @State private var password: String = ""
    @State private var loginError: Bool = false
    
    ...
    
    private func login()
    {
        loginHandler.login(username: username, password: password, error: $loginError)
    }
}

Теперь я пытаюсь изменить значение ошибки внутри моей функции входа в систему:

import Foundation
import SwiftUI

class LoginHandler: ObservableObject
{
    public func login(username: String, password: String, error: Binding<Bool>)
    {
        error = true
    }
}

Но я получаю ошибку

Невозможно присвоить значение: 'error' - это константа 'let'

Что, я думаю, имеет смысл, потому что вы не можете быстро редактировать параметры. Я также пробовал _error = true, потому что однажды видел подчеркивание в сочетании с привязкой, но это тоже не сработало.

Но потом я нашел рабочее решение: error.wrappedValue = true. Моя единственная проблема с этим - следующее утверждение из документации разработчика Apple:

Это свойство обеспечивает первичный доступ к данным значения. Однако у вас нет прямого доступа к wrappedValue. Вместо этого вы используете переменную свойства, созданную с помощью атрибута @Binding.

Хотя я очень рад, что это работает, мне интересно, есть ли лучший способ решить эту ситуацию?

Обновление 20.3.21: новый крайний случай

В разделе комментариев я упомянул случай, когда вы не знаете, сколько раз будет использоваться ваша функция. Теперь я приведу небольшой пример кода:

Представьте себе список загружаемых файлов (DownloadView), которые вы получите из своего бэкэнда:

import SwiftUI

struct DownloadView: View
{
    @EnvironmentObject var downloadHandler: DownloadHandler
    
    var body: some View
    {
        VStack
        {
            ForEach(downloadHandler.getAllDownloadableFiles())
            {
                file in DownloadItemView(file: file)
            }
        }
    }
}

У каждого загружаемого файла есть имя, небольшое описание и собственная кнопка загрузки:

import SwiftUI

struct DownloadItemView: View
{
    @EnvironmentObject var downloadHandler: DownloadHandler
    
    @State private var downloadProgress: Double = -1
    
    var file: File
    
    var body: some View
    {
        HStack
        {
            VStack
            {
                Text(file.name)
                Text(file.description)
            }
            
            Spacer()
            
            if downloadProgress < 0
            {
                // User can start Download
                Button(action: {
                    downloadFile()
                })
                {
                    Text("Download")
                }
            }
            else
            {
                // User sees download progress
                ProgressView(value: $downloadProgress)
            }
        }
    }
    
    func downloadFile()
    {
        downloadHandler.downloadFile(file: file, progress: $downloadProgress)
    }
}

И вот, наконец, DownloadHandler:

import Foundation
import SwiftUI

class DownloadHandler: ObservableObject
{
    public func downloadFile(file: File, progress: Binding<Double>)
    {
        // Example for changing the value
        progress = 0.5
    }
}
-1
KingKevin23 20 Мар 2021 в 15:11

2 ответа

Лучший ответ

Я понимаю, что вы пытаетесь сделать, но в дальнейшем это вызовет проблемы, потому что здесь вы имеете дело с государством. Теперь одно решение:

  • Вы можете просто абстрагировать error классу, но тогда username и password будут в одном месте, а ошибка - в другом.

Тогда идеальное решение - это все абстрагировать в одном месте. Уберите все свойства из поля зрения и сделайте так:

   import SwiftUI

struct LoginView: View
{
    @EnvironmentObject var loginHandler: LoginHandler
    

   
   // login() <-- Call this when needed
    ...
    
    
}

Тогда в вашем классе:

    import Foundation
    import SwiftUI
    
  @Published error: Bool = false
   var username = ""
   var password = ""

    class LoginHandler: ObservableObject
    {
        
       

       public func login()    {
           //If you can't login then throw your error here
           self.error = true
   }
    }

Вам остается только обновить username и пароль`, и вы можете сделать это, например

TextField("username", text: $loginHandler.username)
TextField("username", text: $loginHandler.password)

Изменить: добавление обновления для крайнего случая:

  import SwiftUI

struct ContentView: View {
    var body: some View {
        LazyVGrid(columns: gridModel) {
        
            ForEach(0..<20) { x in
                CustomView(id: x)
            }
        }
    }
    let gridModel = [GridItem(.adaptive(minimum: 100, maximum: 100), spacing: 10),
                     GridItem(.adaptive(minimum: 100, maximum: 100), spacing: 10),
                     GridItem(.adaptive(minimum: 100, maximum: 100), spacing: 10)
    ]
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct CustomView: View {
    @State private var downloaded = false
    @State private var progress = 0
    @ObservedObject private var viewModel = StateManager()
    let id: Int
    

    
    var body: some View {
        showAppropriateView()
    }
    @ViewBuilder private func showAppropriateView() -> some View {
        if viewModel.downloadStates[id] == true {
            VStack {
                Circle()
                    .fill(Color.blue)
                    .frame(width: 50, height: 50)
                
                
            }
        } else {
            Button("Download") {
                downloaded = true
                viewModel.saveState(of: id, downloaded)
            }
        }
    }
}

final class StateManager: ObservableObject {
    @Published var downloadStates: [Int : Bool] = [:] {
        didSet { print(downloadStates)}
    }
    
    func saveState(of id: Int,_ downloaded: Bool) {
        downloadStates[id] = downloaded
    }
}

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

В любом случае, дайте мне знать, было ли это полезно.

0
Sergio Bost 20 Мар 2021 в 19:43

Вы также можете обновить параметры функции, вот пример, здесь не используется привязка или состояние, это не работает!

Теперь я пытаюсь изменить значение ошибки внутри моей функции входа в систему:

Невозможно присвоить значение: 'error' - это константа 'let'

Итак, с помощью этого метода или примера вы можете!


struct ContentView: View {
    
    @State private var value: String = "Hello World!"
    
    var body: some View {
        
        Text(value)
            .padding()
        
        Button("update") {
            
            testFuction(value: &value)
        }
        
    }
}


func testFuction(value: inout String) {
    
    value += " updated!"
}
0
swiftPunk 20 Мар 2021 в 12:40