SwiftUI:如何在单击按钮+ API调用后显示下一个视图

时间:2019-11-12 10:03:09

标签: ios swift swiftui

这听起来像是一件微不足道的任务,但是我找不到适合该问题的解决方案。可能我还没有内部化“ SwiftUI式”的思维方式。

我有一个带按钮的视图。视图加载时,存在一个条件(已经登录?),在这种情况下,视图应直接转到下一个视图。如果单击该按钮,则会触发一个API调用(登录),如果成功,则也应该重定向到下一个视图。

我试图建立一个模型(ObservableObject),其中包含变量“ shouldRedirectToUploadView”,该变量是PassThroughObject。一旦满足视图中onAppear的条件或单击按钮(并且API调用成功),变量就会翻转为true,并告诉观察者更改视图。

在模型中翻转“ shouldRedirectToUploadView”似乎可行,但我无法使视图重新评估该变量,因此新视图将无法打开。

到目前为止,这是我的实现方式

模型

import SwiftUI
import Combine

class SboSelectorModel: ObservableObject {

    var didChange = PassthroughSubject<Void, Never>()

    var shouldRedirectToUpdateView = false {
        didSet {
            didChange.send()
        }
    }

    func fetch(_ text: String) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.shouldRedirectToUpdateView = true
        }
    }
}

视图

import SwiftUI

struct SboSelectorView: View {
    @State var text: String = ""
    @ObservedObject var model: SboSelectorModel

    var body: some View {
        return ZStack {
            if (model.shouldRedirectToUpdateView) {
                UpdateView()
            }
            else {
                Button(action: {
                    self.reactOnButtonClick()
                }) {
                    Text("Start")
                }
            }
        }.onAppear(perform: initialActions)
    }

    public func initialActions() {
        self.model.shouldRedirectToUpdateView = true
    }

    private func reactOnButtonClick() {
        self.model.fetch()
    }
}

在良好的旧版UIKit中,我将只使用ViewController来捕获按钮单击的动作,然后将新视图放入导航堆栈中。我将如何在SwiftUI中做到这一点?

在上面的示例中,我希望视图能够加载,执行 onAppear()函数,该函数执行 initialActions()来翻转模型变量,从而构成视图对更改做出反应并显示 UploadView 。为什么不这样呢?

Programatically navigate to new view in SwiftUIShow a new View from Button press Swift UIHow to present a view after a request with URLSession in SwiftUI?之类的SO示例建议相同的过程。但是,它似乎对我不起作用。我想念什么吗?

提前谢谢!

2 个答案:

答案 0 :(得分:2)

Apple推出了@Published,可完成所有model did change的工作。

这对我有用,看起来更干净。

当模型中的某些内容发生更改时,您还可以使用.onReceive()在视图上执行操作。

class SboSelectorModel: ObservableObject {
    @Published var shouldRedirectToUpdateView = false

    func fetch(_ text: String) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.shouldRedirectToUpdateView = true
        }
    }
}

struct UpdateView: View {
    var body: some View {
        Text("Hallo")
    }
}

struct SboSelectorView: View {
    @State var text: String = ""
    @ObservedObject var model = SboSelectorModel()

    var body: some View {
        ZStack {
            if (self.model.shouldRedirectToUpdateView) {
                UpdateView()
            }
            else {
                Button(action: {
                    self.reactOnButtonClick()
                }) {
                    Text("Start")
                }
            }
        }.onAppear(perform: initialActions)
    }

    public func initialActions() {
        self.model.shouldRedirectToUpdateView = true
    }

    private func reactOnButtonClick() {
        self.model.fetch("")
    }
}

我希望这会有所帮助。

编辑

因此,beta 5的这种情况似乎已改变

这里的工作模型是PassthroughSubject

class SboSelectorModel: ObservableObject {
    let objectWillChange = PassthroughSubject<Bool, Never>()


    var shouldRedirectToUpdateView = false {
        willSet {
            objectWillChange.send(shouldRedirectToUpdateView)
        }
    }

    func fetch(_ text: String) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.shouldRedirectToUpdateView = true
        }
    }
}

答案 1 :(得分:0)

以防万一,有人希望用SwiftUI的方法替代可观察对象之类的东西,这可能很棒,但是当我进行构建时,我注意到我有100个对象,丝毫不喜欢感觉多么复杂。 (哦,我只想输入self.present("nextScene", animated: true)的方法。)我知道其中很大一部分是我的想法,还不完全取决于SwiftUI的生活,但是以防万一还有其他人想要的更多... UIKit可以满足SwiftUI的替代要求,这是一个有效的系统。

我不是专业人士,所以我不知道这是否是最佳的内存管理方式。

首先,创建一个函数,使您可以了解屏幕上的顶部视图控制器。以下代码是从GIT上的db0Company借来的。

import UIKit

extension UIViewController { 

func topMostViewController() -> UIViewController {
        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }
        if let navigation = self.presentedViewController as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }
        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
        }
        return self
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

创建一个枚举-现在这是可选的,但对您的SwiftUI和UIViewControllers很有帮助;出于演示目的,我有2。

enum RootViews { 
    case example, welcome
}

现在,这很有趣。创建一个可以从SwiftUI视图调用的委托,以将您从一个场景移到另一个场景。我叫我的导航代表。

我在此处添加了一些默认的演示样式,以使通过扩展名的通话更加轻松。

import UIKit //SUPER important!

protocol NavigationDelegate {
    func moveTo(view: RootViews, presentation: UIModalPresentationStyle, transition: UIModalTransitionStyle)
}

extension NavigationDelegate {
    func moveTo(view: RootViews) {
        self.moveTo(view: view, presentation: .fullScreen, transition: .crossDissolve)
    }

    func moveTo(view: RootViews, presentation: UIModalPresentationStyle) {
        self.moveTo(view: view, presentation: presentation, transition: .crossDissolve)
    }

    func moveTo(view: RootViews, transition: UIModalTransitionStyle) {
        self.moveTo(view: view, presentation: .fullScreen, transition: transition)
    }
}

在这里,我创建一个RootViewController-经典的Cocoa Touch类UIViewController。这将符合委托,并且是我们实际移动屏幕的位置。

class RootViewController: UIViewController, NavigationDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
    }


    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        self.moveTo(view: .welcome) //Which can always be changed
    }

    //The Moving Function
    func moveTo(view: RootViews, presentation: UIModalPresentationStyle = .fullScreen, transition: UIModalTransitionStyle = .crossDissolve) {
        let newScene = self.returnSwiftUIView(type: view)
        newScene.modalPresentationStyle = presentation
        newScene.modalTransitionStyle = transition

        //Top View Controller
        let top = self.topMostViewController()

        top.present(newScene, animated: true)
    }

    //Swift View switch. Optional, but my Xcode was not happy when I tried to return a UIHostingController in line. 

    func returnSwiftUIView(type: RootViews) -> UIViewController {
        switch type {
        case .welcome:
            return UIHostingController(rootView: WelcomeView(delegate: self))
        case .example:
            return UIHostingController(rootView: ExampleView(delegate: self))
        }
    }
} 

因此,现在,当您创建新的SwiftUI视图时,只需添加导航代表,并在按下按钮时调用它。

import SwiftUI 
import UIKit //Very important! Don't forget to import UIKit

struct WelcomeView: View {
    var delegate: NavigationDelegate?

    var body: some View {
        Button(action: {
           print("full width")
           self.delegate?.moveTo(view: .name)
        }) {
           Text("NEXT")
            .frame(width: UIScreen.main.bounds.width - 20, height: 50, alignment: .center)
            .background(RoundedRectangle(cornerRadius: 15, style: .circular).fill(Color(.systemPurple)))
            .accentColor(.white)
        }
}

最后但并非最不重要的一点,在您的场景委托中,创建您的RootViewController()并将其用作密钥,而不是UIHostingController(rootView: contentView)

Voila。

我希望这可以帮助某个人!对于在那里我更专业的高级开发人员,如果您能找到一种使其更清洁的方法?或使代码变得更糟的任何东西,请放心!