SwiftUI use of #objc AND #objcMembers with BackEndless - swiftui

Here is my struggle. I'm using Backendless to retrieve data and per their documentation I need to use "#objc" prior to the class definition, "#objcMembers", and/or also "#objc(CLASS_NAME)". Here is the ONLY way my code works:
#objc(LogEvent)
#objcMembers class LogEvent: NSObject {
var objectId: String?
var eventType: String?
}
...but this seems wrong. However if I remove "#objc(CLASS-NAME)" or #objcMembers, or if I try to just use "#obj" without the class name then I get this error:
Could not cast value of type 'Swift.Dictionary<Swift.String, Any>'
(0x103094be0) to 'EventLog.LogEvent' (0x100ff1cd0).
The way I read the docs it SHOULD work like this:
#objc
class LogEvent: NSObject {
var objectId: String?
var eventType: String?
}

Related

Pass a published property as binding

I have an ObservableObject with a published dictionary of strings to arrays with arrays with Ints:
class MyObservableObject: ObservableObject {
#Published var myDict: [String: [[Int]]]
}
And I want to pass one array of Ints as a Binding from inside the same class to a function of a different struct:
{
...
func someFunc(key: String, index: Int) {
someStruct.func(myDict[key]![index])
}
...
}
I understand that #Published vars can't be passed as Bindings. I'm still hoping that there's any way to achieve this. I also tried storing a reference to the array in the other struct using an inout variable, also without success.
#Published vars can't be passed as Bindings
It is not true - it is possible, via projected value using '$', but you want to pass not a property, but part of value of a property, and this is different thing.
The context is not clear and this someFunc smells not-well :) - I'd say it is needed some refactoring here, but, anyway, technically it is possible to do what you want using dynamically in-place generated binding, like
func someFunc(key: String, index: Int) {
guard myDict[key] != nil else { return }
someStruct.func(Binding<[Int]>(
get: { self.myDict[key]![index] },
set: { self.myDict[key]![index] = $0 }
))
}

Passing #Published property where #Binding is expected?

When some calculation happens in viewModel I want to present modal view. Normally I need to set some boolean binding for method:
.fullScreenCover(isPresented: $isGalleryPresented) {
GalleryPickerView())
}
where isGalleryPresented, is #State definied in view. However browsing SO, i have found out that I could have property in viewModel:
#Published var isGalleryPresented = false
and then do something like this:
.fullScreenCover(isPresented: $viewModel.isGalleryPresented) {
GalleryPickerView()
}
And this works just fine, although I don't know how. fullScreenCover method argument of type isPresented: Binding<Bool>, and I pass as far as I can tell a publisher. How does this work?
Your viewModel is a wrapped property of #ObservedObject, which provides binding via keypath subscript to wrapped observable object properties.
Here is a corresponding part of declaration:
#available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) #propertyWrapper #frozen public
struct ObservedObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject {
/// A wrapper of the underlying observable object that can create bindings to
/// its properties using dynamic member lookup.
#dynamicMemberLookup #frozen public struct Wrapper {
/// Returns a binding to the resulting value of a given key path.
///
/// - Parameter keyPath : A key path to a specific resulting value.
///
/// - Returns: A new binding.
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> { get }
}

Why can't I force unwrap my $string to use as a TextField value?

This gives me the following error:
Cannot force unwrap value of non-optional type 'Binding<Person?>
I don't understand why I can't force unwrap a binding?
struct Person {
var name: String
}
struct ContentView: View {
#State private var person: Person? = Person(name: "Peter")
var body: some View {
if person != nil {
TextField("", text: $person!.name)
}
}
}
When you declare a State variable in your view, you'll get a Binding to the value by adding a leading $ to the variable's name.
So, in your code, you'll have a $person that is binding to an optional Person type. $person is Binding<Person?>
To pass value to TextField you'll need a Binding<String>. you can't force-unwrap $person because it's not an optional value. It's a Binding to an optional type. To access the name field inside the Person struct, you'll need a Binding<Person> instead.
Fortunately, there's a method to get what you want.
By using this initializer, you'll have a Binding<Person>?. Note that now instead of a Binding to an optional, you have an optional Binding.
You should be able to use this new binding like this:
// Binding($person) returns Binding<Person>?
TextField("", text: Binding($person)!.name)
Update:
As #Jessy mentioned in the comments, instead of force-unwrapping the optional binding, we can use map to transform the returned Binding to a TextField
var body: some View {
Binding($person).map {
TextField("", text: $0.name)
}
}

SwiftUI "Cannot use instance member 'numberOfDevice' within property initializer; property initializers run before 'self' is available" error

The bolded line (ie var text: String...) gives a "Cannot use instance member 'numberOfDevice' within property initializer; property initializers run before 'self' is available" error. Do I need an init? If so where? Is there a different solution?
struct PairView: View {
var theClass = BluetoothManager()
init() {theClass.viewDidLoad()}
var body: some View {
List {
ForEach(0..<BluetoothManager.peripheralArray.count) { number in //iterates thru 0 to array's count
ConnectionView(numberOfDevice: number) // create a ConnectionView for each number
}
}
}
}
//-------
struct ConnectionView: View {
var numberOfDevice: Int
**var text: String = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!**
// 'name' is a String property of the B.M. class's array's 'numberOfDevice index.'
var body: some View {
ZStack{
RoundedRectangle(cornerRadius: 10.0).fill(Color.blue)
Text(text).foregroundColor(Color.black)
}
}
}
You can use read-only computed property with short-hand.
var text: String {
return (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
}
The error you encountered means you can't use the numberOfDevice variable to instantiate another variable. However, you can use the number you pass to your init method.
Try the following:
struct ConnectionView: View {
var numberOfDevice: Int
var text: String
init(numberOfDevice: Int) {
self.numberOfDevice = numberOfDevice
self.text = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
}
...
}
Note: I don't recommend force-unwrapping (!). If possible try to provide a default value.
Also, BluetoothManager looks like a type and not like an instance of a class. Make sure you access the peripheralArray property on the valid object and not on the BluetoothManager type.
You can use lazy keyword for that:
lazy var text: String = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
What is lazy?
lazy means that it will postpone initialization until someone calls the variable and it will not possible if self is not initialized. So you will be sure self is ready before accessing that value.
Why?
When you call numberOfDevice, you are actually calling self.numberOfDevice, but swift is smart enough to let you not explicitly write self keyword.
The issue here is that self is not initialized yet when you are assigning a value to a variable.
So you need to make sure the variable is initialized BEFORE accessing self.

SwiftUI Text(): Using ternary conditional not localizing

I'm having trouble printing localized text conditionally. For example, this localizes properly:
if valueFromDb.isEmpty {
Text("value_is_empty") //localized text
} else {
Text(valueFromDb)
}
It prints some text in the user's language if valueFromDb is empty, or it prints valueFromDb as it is if it's not. However, when I try to use the ternary operator it doesn't work:
Text(valueFromDb.isEmpty ? "value_is_empty" : valueFromDb)
When valueFromDb is empty, it prints "value_is_empty" rather than actual localized text. I get an error (a random one higher up in the hierarchy thanks to SwiftUI) when trying to cast it as LocalizedStringKey.
Edit: To be clear, I know I can do this:
valueFromDb.isEmpty ? Text("value_is_empty") : Text(valueFromDb)
However, I want to put the ternary conditional inside the Text() brackets because I will do this for several views, and each one will have many modifiers, so the code will become quite bloated.
The problem is due to type inference. You have to declare myString to be of type LocalizedStringKey and then everything will work as expected.
When you declare:
#State var mySrtring: LocalizedStringKey = "whatever"
Then:
Text(myString.isEmpty ? "error_text_localized" : myString)
uses this initializer:
public init(_ key: LocalizedStringKey,
tableName: String? = nil,
bundle: Bundle? = nil,
comment: StaticString? = nil)
When you declare it like this:
#State var mySrtring: String = "whatever"
Then:
Text(myString.isEmpty ? "error_text_localized" : myString)
uses this initialiser:
public init(verbatim content: String)
You have to put your valueFromDb in quotations, then it should work fine.
Text(valueFromDb.isEmpty ? "value_is_empty" : "\(valueFromDb)")