I'm new in Swift and I'm trying to do my project more object oriented.
Im making app for using DJI SDK, and I'm trying to change some things in view when some variable changes in composite class.
So I have:
Code in AppController :
#main
struct AppController: App {
#State var djiService = ProductCommunicationService()
var body: some Scene {
WindowGroup {
InfoFormView(djiService: $djiService, rcEngineConn: $rcEngineConn, bridgeStatus: $bridgeStatus, fpvMode: $fpvMode, libMode: $libMode)
}
}
And in ProductCommunicationService there is few variables and logic
class ProductCommunicationService: NSObject {
#Published var registered = false
#Published var connected = false
#Published var enableBridgeMode = false
#Published var bridgeAppIP = "0.0.0.0"
func registerWithSDK() {
.....
What I'm trying to do, is refresh view I have below when som variable in ProductCom.. class changes
struct InfoFormView: View {
#Binding var djiService : ProductCommunicationService
#Binding var rcEngineConn : Bool
#Binding var bridgeStatus : Bool
#Binding var fpvMode : Bool
#Binding var libMode : Bool
var body: some View {
HStack(alignment: .top){
VStack(alignment: .leading, spacing: 20) {
Text("CR Fly Beta").font(.title).bold()
Text("Connected to aircraft: " + (self.djiService.connected ? "Yes": "No")).font(.title)
Text("Connected to RC: " + (self.rcEngineConn ? "Yes": "No")).font(.title)
Text("Bridge Mode Status: " + (self.djiService.enableBridgeMode ? "On" : "Off")).font(.title)
HStack(){
if(self.djiService.connected){
Button("Lets FLY!"){
self.fpvMode = true
}.buttonStyle(.bordered).font(.title2)
Button("Photo Library"){
self.libMode = true
}.buttonStyle(.bordered).font(.title2)
}
}
Is there any way to refresh it when variable changes? I tried everything, also creating ProductComService with inserted variables(which didn't work because of #State)
So I figured it out.
I created a new object of data myObject with implementation of ObservableObject.
This object contained just variables with #Pubslished.
Then in appController I stored #ObservableObject myObject = myObject()
And bridged it to a view and productCommServise.
Don’t forget o #Published and #ObservableObject every where you initialize it
Related
I have code that makes my filter bar disappear when user has tapped on a product (a shirt or pair of trousers). This all takes place in the Marketplace tab.
I think the code will work once the bug is fixed however I cannot get rid of the buh about missing arguments.
The code for marketplace is:
#Binding var shirtData : Shirt!
#Binding var showDetailShirt: Bool
#Binding var trouserData : Trouser!
#Binding var showDetailTrouser: Bool
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 10){
if !showDetailShirt && !showDetailTrouser {
marketplaceFilterBar
}
if selectedMarketplaceFilter == .shirt {
MarketplaceShirtView()
}
if selectedMarketplaceFilter == .trouser {
MarketplaceTrouserView()
}
}
}
Here is code for the TabView
#StateObject var appModel: AppViewModel = .init()
#StateObject var sharedData: SharedDataModel = SharedDataModel()
#Binding var shirtData : Shirt!
#Binding var showDetailShirt: Bool
#Binding var trouserData : Trouser!
#Binding var showDetailTrouser: Bool
// Animation Namespace...
#Namespace var animation
// Hiding Tab Bar...
init(){
UITabBar.appearance().isHidden = true
}
var body: some View {
VStack(spacing: 0){
// Tab View...
TabView(selection: $appModel.currentTab) {
Marketplace(animation: _animation)
.environmentObject(sharedData)
.tag(Tab.Market)
.setUpTab()
Home()
.environmentObject(sharedData)
.tag(Tab.Home)
.setUpTab()
}
On the Marketplace line in the TabView I am getting the error:
Missing arguments for parameters 'shirtData', 'showDetailShirt', 'trouserData', 'showDetailTrouser' in call
UPDATED:
On my MarketplaceShirtView I have the following code:
#StateObject var MarketplaceModel = MarketplaceViewModel()
#State private var selectedMarketplaceFilter: MarketplaceFilterViewModel = .shirt
#Namespace var animation : Namespace.ID
#State var showDetailShirt = false
#State var selectedShirt : Shirt!
// Shared Data...
#EnvironmentObject var sharedData: SharedDataModel
When I use an if statement to determine whether to display the Detail view I get the error: Cannot convert value of type 'Binding<Shirt?>’ to expected argument type 'Binding'
Here is the code:
if selectedShirt != nil && showDetailShirt{
ShirtDetailView(shirtData: $selectedShirt, showDetailShirt: $showDetailShirt,animation: _animation)
}
According to the code you posted MarcetPlace should be initialized like this:
Marketplace(shirtData: $shirtData, showDetailShirt: $showDetailShirt, trouserData: $trouserData, showDetailTrouser: $showDetailTrouser)
Edit:
Well you got a custom initializer and properties without default values there so probably you have to initialize those too.
E.g.
init(shirtData: Binding<Shirt>, .....
Edit 2:
To be more precise, in your TabView:
// Hiding Tab Bar...
init(shirtData: Binding<Shirt?>, showDetailShirt: Binding<Bool>, trouserData: Binding<Trouser?>, showDetailTrouser: Binding<Bool>){
self._shirtData = shirtData
self._showDetailShirt = showDetailShirt
self._trouserData = trouserData
self._showDetailTrouser = showDetailTrouser
UITabBar.appearance().isHidden = true
}
I don´t think you need those implicit unwrappings so instead of #Binding var shirtData : Shirt! use #Binding var shirtData : Shirt and in the initializer init(shirtData: Binding<Shirt?>, would become init(shirtData: Binding<Shirt>,
This are two properties which I declared
struct DashBoardView: View {
#State var isToPush: Bool = false
#ObservedObject var sideBarHandler = SideBarHandler()
Where SideBarHandler is -
class SideBarHandler: ObservableObject {
#Published var isListItemClicked:Bool = false
}
Now I am looking to activate $isToPush based on sideBarHandler.isListItemClicked
Because I want to bind it here
NavigationLink(destination: FavouriteView(), isActive: $isToPush) {// NavLink
You have to consider where the source of truth is for the data.
Based on your description, it seems that the isListItemClicked is the source of truth, so you shouldn't even need a #State variable - use $sideBarHandler.isListItemClicked directly (prefix $ of an #ObservedObject gives you a Binding):
NavigationLink(destination: FavouriteView(), isActive: $sideBarHandler.isListItemClicked)
Of course, if #State var isToPush: Bool is only affected by sideBarHandler.isListItemClicked but otherwise exists independently - i.e. it is a source of truth for this data - then you can use onReceive as suggested by #Asperi to change the isToPush property:
.onReceive(sideBarHandler.$isListItemClicked) {
isToPush = $0
}
(note that $ prefix here accesses a #Published Combine publisher)
Also, unrelated to your question, but if you're instantiating an ObservableObject inside the view, then you should use #StateObject instead of #ObservedObject. The latter is meant for a case when the observable object is created outside of the view.
You can use onReceive somewhere in body, like
struct DashBoardView: View {
#State var isToPush: Bool = false
#ObservedObject var sideBarHandler = SideBarHandler()
var body: some View {
VStack {
// some content here
}
.onReceive(sideBarHandler.$isListItemClicked) {
isToPush = $0
}
}
}
I'm pretty new to SwiftUI (and Swift I haven't touch for a while either) so bear with me:
I have this view:
import SwiftUI
import Combine
var settings = UserSettings()
struct Promotion: View {
#State var isModal: Bool = true
#State private var selectedNamespace = 2
#State private var namespaces = settings.namespaces
var body: some View {
VStack {
Picker(selection: $selectedNamespace, label: Text("Namespaces")) {
ForEach(0 ..< namespaces.count) {
Text(settings.namespaces[$0])
}
}
}.sheet(isPresented: $isModal, content: {
Login()
})
}
}
What I do here, is to call a Login view upon launch, login, and when successful, I set the
var settings
as such in the LoginView
settings.namespaces = ["just", "some", "values"]
my UserSettings class is defined as such
class UserSettings: ObservableObject {
#Published var namespaces = [String]()
}
According to my recently obtained knowledge, my Login view is setting the namespaces property of my UserSettings class. Since this class is an ObservableObject, any view using that class should update to reflect the changes.
However, my Picker remains empty.
Is that because of a fundamental misunderstanding, or am I just missing a comma or so?
You have to pair ObservableObject with ObservedObject in view, so view is notified about changes and refreshed.
Try the following
struct Promotion: View {
#ObservedObject var settings = UserSettings() // << move here
#State var isModal: Bool = true
#State private var selectedNamespace = 2
// #State private var namespaces = settings.namespaces // << not needed
var body: some View {
VStack {
Picker(selection: $selectedNamespace, label: Text("Namespaces")) {
ForEach(namespaces.indices, id: \.self) {
Text(settings.namespaces[$0])
}
}
}.sheet(isPresented: $isModal, content: {
Login(settings: self.settings) // inject settings
})
}
}
struct Login: View {
#ObservedObject var settings: UserSettings // << declare only !!
// ... other code
}
I want increment a variable on Button action and show the value on Text. I can do it with #State var i: Int = 0 variable. But I want to update the value from another class or swift file. So that file cannot access this variable unless it is static. But State can't be static as it fails build with segmentation fault error while building.
So I thought of making it a simple variable. Then updating from that class and then assigning it with a #State variable. But that is not happening because to update variable I'm using another static function on SwiftUIView struct as well.
I can update the static variable but I can't update the View.
heres the code for UIVIew:
struct ContentView: View {
//private var Arr = [String]()
#State static var x: Int = 0 //Causes segmentation fault, Removing static runs but can't access var x
static var testVar: Int = 0
var body: some View {
VStack{
Text("Hello, Time: \(ContentView.testVar)")
Button(action: {
_ = timer().testMe() // calling other class method, is this Okay?
}) {
Text("Button")
}
}
}
static func bigGuy(){
testVar += 1
print("Hello You clicked this time: \(testVar)") // Without next line it can run and print with no issue.
self.x = testVar // Don't want to use this, But how do I update View ?
}
So how do I update this simple view?
Don't use static, instead put that variable into view model and use instance of view model in both views, so one modifies it another presents it, like in below example
class ViewModel: ObservableObject {
#Published var x: Int = 0
}
struct View1: View {
#ObservedObject var vm: ViewModel
var body: some View {
Text("\(mv.x)")
}
}
struct View2: View {
#ObservedObject var vm: ViewModel
var body: some View {
Button("Modify") { self.mv.x += 1 }
}
}
struct ContentView: View {
let vm = ViewModel()
var body: some View {
VStack {
View1(vm: self.vm)
View2(vm: self.vm)
}
}
}
I know in which situations to use #Binding and #Published
like in ObservableObject I generally use #Published, or objectWillChange.send()
And #Binding in subviews to propagate changes to parent
But here I have snippet which seems to be working that uses both #Binding and #Published
in ObservableObject
I consider what is the difference.
#Binding var input: T
#Binding var validation: Validation
#Published var value: T {
didSet {
self.input = self.value
self.validateField()
}
}
init(input: Binding<T>, rules: [Rule<T>], validation: Binding<Validation>) {
self._input = input
self.value = input.wrappedValue
self.rules = rules
self._validation = validation
}
As I tested it seems that if I bind TextField to #Published then didSet is called but if I bind it to #Binding then didSet won't be called.
For a #Binding, I found that didSet was called if you assign to the property directly. Try running this example in an xcode playground:
import SwiftUI
import Combine
struct MyView: View {
#Binding var value: Int {
didSet {
print("didSet", value)
}
}
var body: some View { Text("Hello") }
}
var myBinding = Binding<Int>(
get: { 4 },
set: { v in print("set", v)})
let view = MyView(value: myBinding)
view.value = 99
Output:
set 99
didSet 4
Regarding the difference between #Published and #Binding:
You'll generally use #Binding to pass a binding that originated from some source of truth (like #State) down the view hierarchy.
Use #Published in an ObservableObject to allow a view to react to changes to a property.