Swift UI StateObject Published two side communication - ios

I am new to this SwiftUI and would like to ask some questions.
There are three objects in total.
View1, View2, Object1.
Here's part of my code.
struct View1: View {
#StateObject var object = Object1()
var body: some View {
VStack() {
View2(object: object)
Button(action: {} )
}
}
func isOverchanged() { //doSomething }
}
struct View2: UIViewControllerRepresentable {
#ObservedObject var object = Object1()
}
class Object1: NSObject, ObservableObject {
#Published var mydata: [Double] = []
#State var isOver: Bool = false
}
I want to achieve two goals.
The first one is that "view2" to be notified if there is a change in myData inside "object".
The second one is that isOverChanged function in "view1" to be notified (Or notify the button action closure) if the isOver inside the object changes.
The first goal has been achieved.
But the second one tried a lot without success.
If it is the way of writing ViewController delegate, I can set delegate.onIsOverChanged(). But when it comes to SwiftUI I don't know what to do.
Here is part of the delegate version that may looks like.
protocol ObjectProtocol {
onMyDataChanged()
onIsOverChanged()
}
class View1: UIViewController, ObjectProtocol {
var object: Object1 = Object(delegate: self)
var view2: View2 = View2()
func onMyDataChanged() {
view2.doSomething()
}
func onIsOverChanged() {
//doSomething
}
}
class Object1: NSObject {
weak var delegate: ObjectProtocl?
var isOver = false
func isOverChanged(){
delegate?.onIsOverChanged()
}
func myDataChanged(){
delegate?.onMyDataChanged()
}
}
class View2: UIView {
func doSomething(){ }
}
Really thanks.

Related

Running code when SwiftUI Toggle Changes Value

The general structure of my code is that I have a UIKit app in which I am trying to embed a swiftui view. So I have a file called SettingsViewController which is as follows:
class SettingsViewController: UIViewController {
...
var items: [(SettingsView.Setting)] = ...
var actionsForItems: [() -> Void = []
#State var isOn: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
actionsForItems = ...
...
addSettingCell(isOn: isOn)
let childView = UIHostingController(rootView: SettingsView(settings: items, actionsForSettings: actionsForItems))
addChild(childView)
childView.view.frame = container.bounds
container.addSubview(childView.view)
childView.didMove(toParent: self)
}
...
func addCell(isOn: Bool) {
items.insert((settingName, $isOn as AnyObject) as SettingsView.Setting)
actionsForItems.insert({
self.onSwitchValueChanged(isOn: isOn) //defined later
})
}
}
which creates a view called Settings View which is structured as follows:
struct SettingsView: View {
typealias Setting = (key: String, value: AnyObject?)
var settings: [Setting]
var actions: [() -> Void]
var body: some View {
List(settings.indices) { index in
...
SettingsWithSwitchView(setting: settings[index], action: actions[index], isOn: setting.value as Binding<Bool>)
}
Spacer()
}
}
and SettingsWithSwitchView is as follows:
struct SettingsWithSwitchView: View {
var setting: SettingsView.Setting
var action: () -> Void
#Binding var isOn: Bool {
willSet {
print("willSet: newValue =\(newValue) oldValue =\(isOn)")
}
didSet {
print("didSet: oldValue=\(oldValue) newValue=\(isOn)")
action()
}
}
var body: some View {
HStack {
Text(setting.key)
.foregroundColor(Color("GrayText"))
.font(Font.custom("OpenSans", size: 15))
Spacer()
Toggle(isOn: $isOn) {}
}
}
}
I read in another post here on Stack Overflow that calling didSet on the isOn property should be the way to accomplish this, but I need to call onSwitchValueChanged when the Toggle value is updated, but my current setup does not work. I would really appreciate some help to figure out how to do this. I can update with some other information if necessary.
The thing that ended up working for me was creating a ViewModel which was also an ObservableObject and then setting the action for the toggle inside of .onTapGesture

How to pass data from SwiftUI to ViewController

hoping someone can help me with this, I am building an iOS app with Swift and SwiftUI that launches different Unity scenes with the click of different buttons. I have the Unity part working well thanks to some good tutorials but I am having trouble being able to change scene with different button clicks. The tutorials I have followed set up the Unity code in a view controller that is then launched from a navigation view. I can launch the view controller and change the sceneNumber to whichever one I want and it will load that scene. I can't find away to do that in a way that is set before I launch the view controller.
Here is my View where the LauncherView has two buttons that will launch the view controller, I would like to change which sceneNumber is sent to the ViewController. I also have the problem of when I go back in the app and click through again nothing is displaying. I think this is because the UnityBridge class is already created and in the background somewhere.
Any help would be greatly appreciated! Thanks.
Here is my view code:
var sceneNumber: String = ""
struct MyViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
UnityBridge.getInstance().onReady = {
print("Unity is now ready!")
UnityBridge.getInstance().show(controller: vc)
let api = UnityBridge.getInstance().api
api.test(sceneNumber)
}
return vc
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {}
}
struct ContentView: View {
var body: some View {
VStack{
MyViewController()
}
}
}
struct FirstView: View {
var body: some View {
NavigationView{
VStack{
NavigationLink(destination: ContentView()) { //+ code to set sceneNumber to "1"
Text("Launch Unity Scene 1")
}
Text("")
NavigationLink(destination: ContentView()) { //+code to set sceneNumber to "2"
Text("Launch Unity Scene 2")
}
}
}
}
}
And here is the UnityBridge class code:
import Foundation
import UnityFramework
import SwiftUI
class API: NativeCallsProtocol {
internal weak var bridge: UnityBridge!
/**
Function pointers to static functions declared in Unity
*/
private var testCallback: TestDelegate!
/**
Public API
*/
public func test(_ value: String) {
self.testCallback(value)
}
/**
Internal methods are called by Unity
*/
internal func onUnityStateChange(_ state: String) {
switch (state) {
case "ready":
self.bridge.unityGotReady()
default:
return
}
}
internal func onSetTestDelegate(_ delegate: TestDelegate!) {
self.testCallback = delegate
}
}
class UnityBridge: UIResponder, UIApplicationDelegate, UnityFrameworkListener {
private static var instance : UnityBridge?
internal(set) public var isReady: Bool = false
public var api: API
private let ufw: UnityFramework
public var view: UIView? {
get { return self.ufw.appController()?.rootView }
}
public var onReady: () -> () = {}
public static func getInstance() -> UnityBridge {
print("Hello1.1")
//UnityBridge.instance?.unload()
if UnityBridge.instance == nil {
print("Hello1.2")
UnityBridge.instance = UnityBridge()
}
print("Hello1.3")
return UnityBridge.instance!
}
private static func loadUnityFramework() -> UnityFramework? {
print("Hello2")
let bundlePath: String = Bundle.main.bundlePath + "/Frameworks/UnityFramework.framework"
let bundle = Bundle(path: bundlePath)
if bundle?.isLoaded == false {
bundle?.load()
}
let ufw = bundle?.principalClass?.getInstance()
if ufw?.appController() == nil {
let machineHeader = UnsafeMutablePointer<MachHeader>.allocate(capacity: 1)
machineHeader.pointee = _mh_execute_header
ufw!.setExecuteHeader(machineHeader)
}
return ufw
}
internal override init() {
print("Hello3")
self.ufw = UnityBridge.loadUnityFramework()!
self.ufw.setDataBundleId("com.unity3d.framework")
self.api = API()
super.init()
self.api.bridge = self
self.ufw.register(self)
FrameworkLibAPI.registerAPIforNativeCalls(self.api)
ufw.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: nil)
}
public func show(controller: UIViewController) {
print("Hello4")
if self.isReady {
self.ufw.showUnityWindow()
}
if let view = self.view {
controller.view?.addSubview(view)
}
}
public func unload() {
print("Hello5")
self.ufw.unloadApplication()
}
internal func unityGotReady() {
print("Hello6")
self.isReady = true
onReady()
}
internal func unityDidUnload(_ notification: Notification!) {
print("Hello7")
ufw.unregisterFrameworkListener(self)
UnityBridge.instance = nil
}
}

Pass variable from UIViewController to SwiftUI View

I can't find a way or a good tutorial to explain how to pass the value of a variable (String or Int) that is owned by a UIViewController to a SwiftUI view that is calling the view.
For example:
class ViewController: UIViewController {
var myString : String = "" // variable of interest
....
func methodThatChangeValueOfString(){
myString = someValue
}
}
// to make the view callable on SwiftUI
extension ViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = ViewController
public func makeUIViewController(context: UIViewControllerRepresentableContext<ViewController>) -> ViewController {
return ViewController()
}
func updateUIViewController(_ uiViewController: ViewController, context: UIViewControllerRepresentableContext<ViewController>) {
}
}
In SwiftUI I'll have
struct ContentView: View {
var body: some View {
ViewController()
}
}
How can I take myString of the ViewController and use it in ContentView?
Thanks in advance
Use MVVM pattern it is what is recommended with SwiftUI.
Share a ViewModel between your SwiftUI View and your UIKit ViewController.
I suggest you start with the basic Apple SwiftUI tutorials. Specifically how to "Interface with UIKit"
https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit
import SwiftUI
struct SwiftUIView: View {
#StateObject var sharedVM: SharedViewModel = SharedViewModel()
var body: some View {
VStack{
UIKitViewController_UI(sharedVM: sharedVM)
Text(sharedVM.myString)
}
}
}
class SharedViewModel: ObservableObject{
#Published var myString = "init String"
}
//Not an extension
struct UIKitViewController_UI: UIViewControllerRepresentable {
typealias UIViewControllerType = UIKitViewController
var sharedVM: SharedViewModel
func makeUIViewController(context: Context) -> UIKitViewController {
return UIKitViewController(vm: sharedVM)
}
func updateUIViewController(_ uiViewController: UIKitViewController, context: Context) {
}
}
class UIKitViewController: UIViewController {
let sharedVM: SharedViewModel
var runCount = 0
init(vm: SharedViewModel) {
self.sharedVM = vm
super.init(nibName: nil, bundle: nil)
//Sample update mimics the work of a Delegate or IBAction, etc
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
self.runCount += 1
self.methodThatChangeValueOfString()
if self.runCount == 10 {
timer.invalidate()
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func methodThatChangeValueOfString(){
sharedVM.myString = "method change" + runCount.description
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}

Sheet not dismissing with presentation mode in SwiftUI/UIKit?

I have a SwiftUI view that displays a sheet using a #State variable:
import SwiftUI
struct AdRevenue: View {
#State var playAd = false
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Button(action: {
self.playAd = true
})
{
Text("Play Ad")
}.sheet(isPresented: $playAd) {
Ads()}
}
}
This is the UIViewRepresentable sheet:
struct Ads: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: Context) -> UIViewController {
return ViewController()
}
func updateUIViewController(_ uiView: UIViewController, context: Context) {
}
class ViewController: UIViewController, GADRewardedAdDelegate, AdManagerRewardDelegate {
var rewardedAd: GADRewardedAd?
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
override func viewDidLoad() {
super.viewDidLoad()
AdManager.shared.loadAndShowRewardAd(AdIds.rewarded.rawValue, viewController: self)
AdManager.shared.delegateReward = self
}
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
print("Reward received: \(reward.type), amount \(reward.amount).")
}
}
}
Within AdManager, a function is called as such:
func rewardAdDidClose() {
let mom = Ads()
mom.presentationMode.wrappedValue.dismiss()
print("mom.presentationMode.wrappedValue.dismiss()")
}
Yet although I see the presentationMode message when I run it, the sheet doesn't get dismissed. Is it possible to dismiss the sheet like this?
Is this because you have two Ads values?
The first is created by SwiftUI (inside the AdRevenue view), so its presentationMode property is likely to be correctly wired-up to the app's environment.
But later, within AdManager, you're creating another Ads value, and expecting it to have a usable presentationMode environment object. It's being created outside of SwiftUI, so cannot know about the environment of the rest of your app.
I'd try passing the Ads value into your AdManager, rather than having AdManager create a new one.

Change SwiftUI Text from UIViewController

I'm quite new to Swift and absolute new to SwiftUI.
I'm trying to combine UIViewController Google Maps and modern SwiftUI.
In SwiftUI i have a few Text objects, and I want my GmapsController class to be able to modify these Text values, and redraw SwiftUI struct.
My main struct:
var _swiftUIStruct : swiftUIStruct = swiftUIStruct() // to have access in GmapsController
struct GoogMapView: View {
var body: some View {
let gmap = GmapsControllerRepresentable()
return ZStack {
gmap
_swiftUIStruct
}
}
}
UIViewControllerRepresentable wrapper of GmapsController :
struct GmapsControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<GmapsControllerRepresentable>) -> GmapsController {
return GmapsController()
}
func updateUIViewController(_ uiViewController: GmapsController, context: UIViewControllerRepresentableContext<GmapsControllerRepresentable>) {}
}
The GmapsController itself:
class GmapsController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
var locationManager = CLLocationManager()
var mapView: GMSMapView!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
let camera = GMSCameraPosition.camera(
withLatitude: (locationManager.location?.coordinate.latitude)!,
longitude: (locationManager.location?.coordinate.longitude)!,
zoom: 15
)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.delegate = self
self.view = mapView
}
// HERE I WANT TO CHANGE SOME swiftUIStruct Text FIELDS
// calls after map move
func mapView(_ mapView: GMSMapView, idleAt сameraPosition: GMSCameraPosition) {
_swiftUIStruct.someTxt = "I hope this will work" // compilation pass, but value doesn't change
}
}
And the swiftUIStruct struct:
struct swiftUIStruct {
#State var someTxt = ""
var body: some View {
Text(someTxt) // THE TEXT I WISH I COULD CHANGE
}
}
Googling this a whole day just made me feel dumb, any help is appreciated.
I hope my example code helps. Basically, move the model data outside, and pass it along, and change it. If you run this code, you will see the text "I hope this will work", NOT "Initial Content".
import SwiftUI
class ViewModel: ObservableObject {
#Published var someTxt = "Initial Content"
}
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
ZStack {
GoogleMapsView(viewModel: viewModel)
Text(viewModel.someTxt)
}
}
}
struct GoogleMapsView: UIViewControllerRepresentable {
var viewModel: ViewModel
func makeUIViewController(context: Context) -> GmapsController {
let controller = GmapsController()
controller.viewModel = viewModel
return controller
}
func updateUIViewController(_ uiViewController: GmapsController, context: Context) {}
}
class GmapsController: UIViewController {
var viewModel: ViewModel?
override func viewDidLoad() {
viewModel?.someTxt = "I hope this will work"
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
return ContentView()
}
}

Resources