这听起来像是一件微不足道的任务,但是我找不到适合该问题的解决方案。可能我还没有内部化“ 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 SwiftUI或Show a new View from Button press Swift UI或How to present a view after a request with URLSession in SwiftUI?之类的SO示例建议相同的过程。但是,它似乎对我不起作用。我想念什么吗?
提前谢谢!
答案 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。
我希望这可以帮助某个人!对于在那里我更专业的高级开发人员,如果您能找到一种使其更清洁的方法?或使代码变得更糟的任何东西,请放心!