Pass a published property as binding - swiftui

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 }
))
}

Related

How to update variable in List For Each

struct NotesView: View {
#State var notesArray = [Note]()
public var deleteid: String
var body: some View {
List{
ForEach(notesArray, id: \._id) { notesArray in
NavigationLink(destination: AddNotesView(addNotesViewIdentifier: "updateNote", id: notesArray._id, title: notesArray.title, note: notesArray.note, noteDate: notesArray.date)){
HStack {
Text(notesArray.title)
deleteid = notesArray._id //ERROR - Cannot assign to property: 'self' is immutable
}
}
}
.onDelete(perform: deleteNoteAtIndex)
}
}
func deleteNoteAtIndex(at offsets: IndexSet){ APIFunctions.functions.DeleteNote(id: _id) }
I was expecting the variable "deleteid" to update.
I assumed you can modify any variable by calling that variable and set it equal to a new value.
Like this
First declare variable:
var deleteid: String
next modify variables string valve
deleteid = notesArray._id
A couple of things:
This isn't directly related to your question, but may help you navigation your own code better... When you create a ForEach view to iterate over an array, you should use a different name for the value that represents each element in the iteration. Here, you're using the name notesArray for your array of notes, then creating a second local variable called notesArray for the loop. That variable inside the block will be an instance of Note, so I'd name it note, e.g.:
ForEach(notesArray, id: \._id) { note in
NavigationLink(destination: AddNotesView(addNotesViewIdentifier: note._id, // etc
}
If you want variables to be modifiable inside views, they should be #State variables. This is important due to the way Swift struct lifecycles work, and how the SwiftUI rendering system loads and reloads structs as it works out what has changed.
I'm not entirely sure what deleteid is supposed to represent here, and it's possible you don't need it at all. If you're using the onDelete modifier to implement SwiftUI's native swipe-to-delete system, SwiftUI will give you an IndexSet, which is a collection (usually of just one) of the positions of the item(s) to delete in the array.
From there, you can find the item(s) at each index and then either remove them, or lookup some other value (e.g., their _id attribute) and do some other operation on them.
So the method you might call in onDelete could look something like:
func deleteNoteAtIndex(offsets: IndexSet) {
// get the array objects that the offsets point to
let notes = offsets.map { noteArray[$0] }
for note in notes {
APIFunctions.functions.deleteNote(id: note._id)
}
}

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.

Does not conform to String protocol SwiftUI Picker View

I have a simple struct that I will build upon. Right now it has one field, an Int.
struct Card: CustomStringConvertible {
let value: Int
init(value: Int) {
self.value = value
}
var description: String {
return "\(String(value))"
}
}
If I do this, I get the Card to print it's value
let c = Card(value: 1)
print(c)
Now if I put an array of Cards in a CardController like this:
class CardController: ObservableObject {
#Published
var cards: [Card] = [
Card(value: 1),
Card(value: 2),
Card(value: 3)
]
Picker(selection: $selectedCardValue, label: Text("Choose a card")) {
ForEach(0..<cardController.cards.count) {
Text(self.cardController.cards[$0])
}
}
Text("You selected \(selectedCardValue)")
I'll get the error Initializer 'init(_:)' requires that 'Card' conform to StringProtocol. I'm not sure why I get this error. If I instead just change the cards to a type of [String] and values ["1", "2", "3"], the code works fine.
Any idea what's wrong here?
As E.Coms noted, the solution is to use one of the following:
Text(self.cardController.cards[$0].description)
Text(String(describing: self.cardController.cards[$0]))
Here's an explanation of why you have to do this inside the Text initializer, but not print().
Look at the two initializers for Text:
init(verbatim content: String) (docs)
init<S>(_ content: S) where S : StringProtocol (docs)
You must pass either a String or a Substring, the only two types conforming to StringProtocol. In this case, even though your type conforms to CustomStringConvertible, you are still passing a Card.
Contrast this with something like the print function:
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") (docs)
Notice that the print function's arguments are denoted by Any, which is explained as
Any can represent an instance of any type at all, including function types.
The print function then converts whatever you passed it to a String:
The textual representation for each item is the same as that obtained by calling String(item).
String has an initializer which takes a type conforming to CustomStringConvertible and returns the description property.
So the reason you can write print(Card()) and not Text(Card() is because the print function has an intermediate step through String that can understand your conformance to CustomStringConvertible, but Text does not. If Text allowed you to pass it any type, it would be both more ambiguous ("What is the text representation of this type?" is not necessarily immediately apparent, as it depends on a hierarchical set of protocols), and more work for the SwiftUI system, which is already doing a lot.
You may miss the description by chance.
ForEach(0..<cardController.cards.count) {
Text(self.cardController.cards[$0].description)
}

Global variable in swift 3

I have the variable provincias_ws which has the answer of a service, so far everything is fine but I want to know how I make the variable provincias_ws is global since I want to print it in the textFieldDidBeginEditing method
class ViewCtrl: UIViewController, ValidationDelegate, UITextFieldDelegate {
#IBOutlet weak var txt_ciudadU: SearchTextField!
override func viewDidLoad() {
super.viewDidLoad()
opciones_autocompletado()
txt_ciudadU.delegate = self
}
func opciones_autocompletado(){
txt_provinciaU.inlineMode = true
txt_ciudadU.inlineMode = true
Services.objServices.ServProv().then{
data -> Void in
let res = JSON(data)
let status = res["status"].boolValue
if(status){
let provincias_ws = res["data"]["provincias"] //How to make this variable global
}else{
let error = res["error"]["error_data"].stringValue
print(error)
}
SVProgressHUD.dismiss()
UIApplication.shared.endIgnoringInteractionEvents()
}.catch{
error -> Void in
SVProgressHUD.dismiss()
UIApplication.shared.endIgnoringInteractionEvents()
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
print(provincias_ws)
}
}
Thank you in advance
One can create a "global" variable (meaning it can be access from anywhere in the project) by defining it outside of the class block.
However, it looks like what you want is a class instance variable.
Your text field txt_ciudadU is already an example of an instance variable or property of the class ViewCtrl.
This would be the same, just without #IBOutlet weak.
So would probably be (assuming it's a String):
var provincias_ws: String?
Note the ? declares it as an optional value because it will not be set until it gets set inside your block. You could also initialize it to some default or empty value (e.g. var provincias_ws = "") or you could use ! to indicate you won't be trying to unwrap it when it's nil, although it's better not to rely on that because it is error-prone. What you choose to do depends on your needs.
And you would put that at the same hierarchical level as your text field variable (i.e. right above or below it, although it could go anywhere really, as long as it's not inside a function or another block).
And you would get rid of the current let next to provincias_ws and prepend it with self. i.e.:
self.provincias_ws = res["data"]["provincias"]
The self. is necessary because it's inside a block and the compiler needs that for context.

Swift3 - how to call class or struct functions by name

As I come from a JS background this is how I'd call a function by name stored in a variable:
var obj = {
foobar: function(param) {
// do something
}
};
var key = "foobar";
obj[key](123);
Now I would like to recreate this in Swift, for example:
struct obj = {
func foobar(param) {
// do something
}
}
let key:String = "foobar"
obj[key](123)
The above code unfortunately gives Type 'obj.Type' has no subscript members
Is there any way to call functions by names in a Struct, Class or a Dictionary (if it's even possible to store functions in Dicts?)
EDIT - MORE CONTEXT:
I have a user-supplied array of things say:
let arr = ["apples", "oranges", "pears"]
but this array can be as long as 20 items. Based on each item of the array I need to perform certain action. So I iterate over the array:
for (key, _) in arr {
if (key == "apples") {
handleApples()
}
if (key == "oranges") {
handleOranges()
}
// and so on...
}
Sure, I can have a function with a simple switch that would consist of 20 cases but that's far from ideal. What if my array grows to say 100 items?
I was hoping to achieve something similar to this:
for (key, _) in arr {
myClass[key]()
}
Is there any way to call functions by names in a Struct, Class or a Dictionary (if it's even possible to store functions in Dicts?)
Yes you can store a function into a dictionary
Let's define a function type
typealias FuncType = () -> ()
and 2 functions
func func0() {
print("Apples")
}
func func1() {
print("Oranges")
}
Now we can create a dictionary where the key is String and the value is FuncType
let dict : [String:FuncType] = [
"Apples" : func0,
"Oranges" : func1
]
And of course we can invoke a function stored into the dictionary
dict["Apples"]?() // prints "Apples"
I think this might be what you're looking for:
var obj: [String: Int] = {
// do something, like call foobar(param) that's defined outside of the variable
// or just manipulate data directly in here
return ["foobar": 123]
// or whatever dictionary you want that matches the type you defined for obj
}()
Kind of tough to give you a better answer without you posting the kind of output or behavior you're looking for.