如何通过 Swift UI 中的按钮单击将文本字段值传递给视图控制器
How to pass Textfield value to view controller through button click in Swift UI
我在使用 swiftUI 和视图控制器使 Admob 横幅的简单 UI 工作时遇到问题。
控制器:
import UIKit
import Foundation
import GoogleMobileAds
import ToastViewSwift
public class AdsScreenViewController: UIViewController, GADBannerViewDelegate {
var auID = ""
init (auID: String){
self.auID = auID
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
var bannerView: GADBannerView!
bannerView = GADBannerView(adSize: kGADAdSizeBanner)
addBannerViewToView(bannerView)
bannerView.adUnitID = auID
bannerView.rootViewController = self
bannerView.load(GADRequest())
bannerView.delegate = self
}
func addBannerViewToView(_ bannerView: GADBannerView) {
bannerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bannerView)
view.addConstraints(
[NSLayoutConstraint(item: bannerView,
attribute: .bottom,
relatedBy: .equal,
toItem: bottomLayoutGuide,
attribute: .top,
multiplier: 1,
constant: 0),
NSLayoutConstraint(item: bannerView,
attribute: .centerX,
relatedBy: .equal,
toItem: view,
attribute: .centerX,
multiplier: 1,
constant: 0)
])
}
public func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
print("bannerViewDidReceiveAd")
}
public func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) {
print("bannerView:didFailToReceiveAdWithError: \(error.localizedDescription)")
}
public func bannerViewDidRecordImpression(_ bannerView: GADBannerView) {
print("bannerViewDidRecordImpression")
}
public func bannerViewWillPresentScreen(_ bannerView: GADBannerView) {
print("bannerViewWillPresentScreen")
}
public func bannerViewWillDismissScreen(_ bannerView: GADBannerView) {
print("bannerViewWillDIsmissScreen")
}
public func bannerViewDidDismissScreen(_ bannerView: GADBannerView) {
print("bannerViewDidDismissScreen")
}
}
Swift UI:
import SwiftUI
import UIKit
struct TestAdsView: View {
@State private var auID = ""
@State private var auType = 1
@State private var isPresented = false
var body: some View {
List {
VStack(alignment: .leading, content: {
Text("AdUnit")
.font(.footnote).fontWeight(.medium)
TextField("adunitid", text: $auID)
.font(.headline)
})
VStack(alignment: .leading, content: {
Button(action: {
self.auID = auID
}, label: {
HStack {
Text("Show Ad")
}
})
BannerViewController(auID: auID)
}
)}
}
struct TestAdsView_Previews: PreviewProvider {
static var previews: some View {
TestAdsView()
}
}
struct BannerViewController: UIViewControllerRepresentable {
var auID: String
public typealias UIViewControllerType = UIViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<BannerViewController>) -> BannerViewController.UIViewControllerType {
return AdsScreenViewController(auID: auID)
}
func updateUIViewController(_ uiViewController: BannerViewController.UIViewControllerType, context: UIViewControllerRepresentableContext<BannerViewController>) {
let controller = AdsScreenViewController(auID: auID)
controller.auID = self.auID
}
}
一切编译正常,运行时显示 TextView。但是当输入 id 时它没有调用 bannerView.load,我认为 UIViewControllerRepresentable 自动更新就像视图上的侦听器一样,它应该被调用但没有任何反应。
这是一个小例子。就像我说的,我现在没有 Google 广告,但它应该很简单
import SwiftUI
struct TestAdsView: View {
@StateObject var vm: AdsScreenViewModel = AdsScreenViewModel()
var body: some View {
VStack{
Text(vm.adStatus.rawValue)
TextField("adId", text: $vm.adUnitId)
Button("load Ads", action: {
vm.loadAds()
})
//You might have to play with the position of this.
AdsScreenView_UI(viewModel: vm).frame(width: 0, height: 0)
}
}
}
//This is the source of truth the user input will be held here
class AdsScreenViewModel: ObservableObject, MyAdsViewModelProtocol{
///reference to UIKit
var uiViewController: MyAdsViewControllerProtocol? = nil
@Published var adUnitId: String = ""
@Published var adStatus: AdStatus = .unknown
//MARK: MyAdsViewControllerProtocol
func loadAds() {
print(#function)
uiViewController?.loadAds()
}
func setAdStatus(adStatus: AdStatus) {
print(#function)
self.adStatus = adStatus
}
func getAdId() -> String {
print(#function)
return adUnitId
}
}
struct AdsScreenView_UI: UIViewControllerRepresentable{
@ObservedObject var viewModel: AdsScreenViewModel
func makeUIViewController(context: Context) -> some AdsScreenViewController {
print(#function)
return AdsScreenViewController(viewModel: viewModel)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
print(#function)
}
}
//This can mirror the google sample
class AdsScreenViewController: UIViewController, MyAdsViewControllerProtocol {
///SwiftUI Delegate
var viewModel: MyAdsViewModelProtocol
init(viewModel: MyAdsViewModelProtocol) {
print(#function)
self.viewModel = viewModel
super.init(nibName: nil, bundle: .main)
//Link between UIKit and SwiftUI
self.viewModel.uiViewController = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
print(#function)
viewModel.setAdStatus(adStatus: .initialized)
//Put the current code you have here
}
//MARK: MyAdsViewModelProtocol
func loadAds() {
print(#function)
print("ad id \(viewModel.getAdId())")
viewModel.setAdStatus(adStatus: .loading)
//You would load here not in viewDidLoad
}
}
//Protocols aren't needed but it makes the code reusable and you can see the connection protocol = interface
protocol MyAdsViewModelProtocol{
///Reference to the google view controller
var uiViewController: MyAdsViewControllerProtocol? { get set }
///Tells the viewController to load the ad
func loadAds()
///Retrieves the AdId
func getAdId() -> String
///Sets the Ad Status
func setAdStatus(adStatus: AdStatus)
}
protocol MyAdsViewControllerProtocol: UIViewController{
///Reference to the SwiftUI ViewModel
var viewModel: MyAdsViewModelProtocol { get set }
///Tells Google to load the ad
func loadAds()
}
enum AdStatus: String{
case initialized
case loading
case unknown
case error
}
struct TestAdsView_Previews: PreviewProvider {
static var previews: some View {
TestAdsView()
}
}
我在使用 swiftUI 和视图控制器使 Admob 横幅的简单 UI 工作时遇到问题。
控制器:
import UIKit
import Foundation
import GoogleMobileAds
import ToastViewSwift
public class AdsScreenViewController: UIViewController, GADBannerViewDelegate {
var auID = ""
init (auID: String){
self.auID = auID
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
var bannerView: GADBannerView!
bannerView = GADBannerView(adSize: kGADAdSizeBanner)
addBannerViewToView(bannerView)
bannerView.adUnitID = auID
bannerView.rootViewController = self
bannerView.load(GADRequest())
bannerView.delegate = self
}
func addBannerViewToView(_ bannerView: GADBannerView) {
bannerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bannerView)
view.addConstraints(
[NSLayoutConstraint(item: bannerView,
attribute: .bottom,
relatedBy: .equal,
toItem: bottomLayoutGuide,
attribute: .top,
multiplier: 1,
constant: 0),
NSLayoutConstraint(item: bannerView,
attribute: .centerX,
relatedBy: .equal,
toItem: view,
attribute: .centerX,
multiplier: 1,
constant: 0)
])
}
public func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
print("bannerViewDidReceiveAd")
}
public func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) {
print("bannerView:didFailToReceiveAdWithError: \(error.localizedDescription)")
}
public func bannerViewDidRecordImpression(_ bannerView: GADBannerView) {
print("bannerViewDidRecordImpression")
}
public func bannerViewWillPresentScreen(_ bannerView: GADBannerView) {
print("bannerViewWillPresentScreen")
}
public func bannerViewWillDismissScreen(_ bannerView: GADBannerView) {
print("bannerViewWillDIsmissScreen")
}
public func bannerViewDidDismissScreen(_ bannerView: GADBannerView) {
print("bannerViewDidDismissScreen")
}
}
Swift UI:
import SwiftUI
import UIKit
struct TestAdsView: View {
@State private var auID = ""
@State private var auType = 1
@State private var isPresented = false
var body: some View {
List {
VStack(alignment: .leading, content: {
Text("AdUnit")
.font(.footnote).fontWeight(.medium)
TextField("adunitid", text: $auID)
.font(.headline)
})
VStack(alignment: .leading, content: {
Button(action: {
self.auID = auID
}, label: {
HStack {
Text("Show Ad")
}
})
BannerViewController(auID: auID)
}
)}
}
struct TestAdsView_Previews: PreviewProvider {
static var previews: some View {
TestAdsView()
}
}
struct BannerViewController: UIViewControllerRepresentable {
var auID: String
public typealias UIViewControllerType = UIViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<BannerViewController>) -> BannerViewController.UIViewControllerType {
return AdsScreenViewController(auID: auID)
}
func updateUIViewController(_ uiViewController: BannerViewController.UIViewControllerType, context: UIViewControllerRepresentableContext<BannerViewController>) {
let controller = AdsScreenViewController(auID: auID)
controller.auID = self.auID
}
}
一切编译正常,运行时显示 TextView。但是当输入 id 时它没有调用 bannerView.load,我认为 UIViewControllerRepresentable 自动更新就像视图上的侦听器一样,它应该被调用但没有任何反应。
这是一个小例子。就像我说的,我现在没有 Google 广告,但它应该很简单
import SwiftUI
struct TestAdsView: View {
@StateObject var vm: AdsScreenViewModel = AdsScreenViewModel()
var body: some View {
VStack{
Text(vm.adStatus.rawValue)
TextField("adId", text: $vm.adUnitId)
Button("load Ads", action: {
vm.loadAds()
})
//You might have to play with the position of this.
AdsScreenView_UI(viewModel: vm).frame(width: 0, height: 0)
}
}
}
//This is the source of truth the user input will be held here
class AdsScreenViewModel: ObservableObject, MyAdsViewModelProtocol{
///reference to UIKit
var uiViewController: MyAdsViewControllerProtocol? = nil
@Published var adUnitId: String = ""
@Published var adStatus: AdStatus = .unknown
//MARK: MyAdsViewControllerProtocol
func loadAds() {
print(#function)
uiViewController?.loadAds()
}
func setAdStatus(adStatus: AdStatus) {
print(#function)
self.adStatus = adStatus
}
func getAdId() -> String {
print(#function)
return adUnitId
}
}
struct AdsScreenView_UI: UIViewControllerRepresentable{
@ObservedObject var viewModel: AdsScreenViewModel
func makeUIViewController(context: Context) -> some AdsScreenViewController {
print(#function)
return AdsScreenViewController(viewModel: viewModel)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
print(#function)
}
}
//This can mirror the google sample
class AdsScreenViewController: UIViewController, MyAdsViewControllerProtocol {
///SwiftUI Delegate
var viewModel: MyAdsViewModelProtocol
init(viewModel: MyAdsViewModelProtocol) {
print(#function)
self.viewModel = viewModel
super.init(nibName: nil, bundle: .main)
//Link between UIKit and SwiftUI
self.viewModel.uiViewController = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
print(#function)
viewModel.setAdStatus(adStatus: .initialized)
//Put the current code you have here
}
//MARK: MyAdsViewModelProtocol
func loadAds() {
print(#function)
print("ad id \(viewModel.getAdId())")
viewModel.setAdStatus(adStatus: .loading)
//You would load here not in viewDidLoad
}
}
//Protocols aren't needed but it makes the code reusable and you can see the connection protocol = interface
protocol MyAdsViewModelProtocol{
///Reference to the google view controller
var uiViewController: MyAdsViewControllerProtocol? { get set }
///Tells the viewController to load the ad
func loadAds()
///Retrieves the AdId
func getAdId() -> String
///Sets the Ad Status
func setAdStatus(adStatus: AdStatus)
}
protocol MyAdsViewControllerProtocol: UIViewController{
///Reference to the SwiftUI ViewModel
var viewModel: MyAdsViewModelProtocol { get set }
///Tells Google to load the ad
func loadAds()
}
enum AdStatus: String{
case initialized
case loading
case unknown
case error
}
struct TestAdsView_Previews: PreviewProvider {
static var previews: some View {
TestAdsView()
}
}