Simple assignment operation in init method does not work swiftui - mapkit

I am new in iOS and, right now, trying to implement my own init method in swiftui view. Init has one parameter and have to assign value to two States. But, whatever I do, it does not fill them properly. I use MapKit and CLLocationCoordinate2D for geolocation.
Just to add, coordinates: CLLocationCoordinate2D in init has right value, but state has not.
The view I am implement here is ModalView
Any idea what to do? Here is my code.
#State var coordinate = CLLocationCoordinate2D()
#State private var name: String = ""
#State private var price: Decimal = 0
#State private var priceStr: String = ""
#State private var showAlert = false
#State private var location = ParkPlaceRespons(id: -1, name: "", price: -0, longitude: 18.42645, latitude: 43.85623)
#State private var mapArray = [ParkPlaceRespons]()
var alert: Alert{
Alert(title: Text("Error"), message: Text("Your price input is not in right format"), dismissButton: .default(Text("Ok")))}
init(coordinates: CLLocationCoordinate2D){
print(coordinates) // CLLocationCoordinate2D(latitude: 43.85613, longitude: 18.42745)
self.coordinate = coordinates
print(self.coordinate) // CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
self.coordinate.latitude = coordinates.latitude
self.coordinate.longitude = coordinates.longitude
self.location.longitude = coordinates.longitude
self.location.latitude = coordinates.latitude
print(location) // Does not work as well
self.mapArray.append(location)
print(mapArray) // Empty array
}

When you look into documentation about #State you will find this fragment:
Only access a state property from inside the view’s body (or from
functions called by it). For this reason, you should declare your
state properties as private, to prevent clients of your view from
accessing it.
Therefore you should not set the coordinates, location or mapArray in the initializer, if you find that it is necessary to set the varaibles in the initializer you should either declare them as a #Binding or #ObservedObject.

Related

Jetpack compose and Kotlin, dynamic UI losing values on recomp

I am making a dynamic UI using kotlin and Jetpack compose and storing the information in an object box database.
The aim is that i will have a composable that starts off with 1 initial item that is empty and when the contents of the textbox have been filled in would allow the red "+" button to be clicked and then another textfield would appear. These values will need to be able to be edited constantly all the way until the final composable value is stored. The button changes colour currently and the states are fine with the button so i can add and remove rows
The data comes in as a string and is converted into a Hashmap<Int, String>. The int is used to store the position in the map being edited and the string would be the text value.
Using log messages i see that the information is updated in the list and for recomp sake i instantly store the value of the edited list in a converted json string.
At the moment:
When i scroll past the composable it resets and looks like the initial state (even if i have added multiple rows)
Log messages show that my hashmap has the values from before e.g. {"0":"asdfdsa"} but the previous positions are ignored and as the previous information would still be present but not shown on the UI when i enter it into the first field again (the others are not visible at the time) {"0":"asdfdsa","0":"hello"}. This would later cause an error when trying to save new data to the list because of the duplicate key
In the composables my hashmap is called textFields and is defined like this. Number is used to determine how many textfields to draw on the screen
val textFields = remember { getDataStringToMap(data.dataItem.dataValue) }
val number = remember { mutableStateOf(textFields.size) }
the method to getDataStringToMap is created like this
private fun getDataMapToString(textFieldsMap: HashMap<Int, String>): String {
val gson = Gson()
val newMap = hashMapOf<Int, String>()
for (value in textFieldsMap){
if (value.value .isNotBlank()){
newMap[value.key] = value.value
}
}
return gson.toJson(newMap)
}
and the method to getDataStringToMap is created like this (I explicitly define the empty hashmap type because its more readable for me if i can see it)
private fun getDataStringToMap(textsFieldsString: String): HashMap<Int, String> {
val gson = Gson()
return if (textsFieldsString.isBlank()) {
hashMapOf<Int, String>(0 to "")
} else {
val mapType = HashMap<Int, String>().javaClass
gson.fromJson(textsFieldsString, mapType)
}
the composables for the textfields are called like this
items(number.value) { index ->
listItem(
itemValue = textFields[index].orEmpty(),
changeValue = {
textFields[index] = it
setDataValue(getDataMapToString(textFields))
},
addItem = {
columnHeight.value += itemHeight
scope.launch {
scrollState.animateScrollBy(itemHeight)
}
},
deleteItem = {
columnHeight.value -= itemHeight
scope.launch {
scrollState.animateScrollBy(-itemHeight)
}
},
lastItem = index == number.value - 1,
index = index
)
}
Edited 30/12/2022
Answer from #Arthur Kasparian solved issues. Change to rememberSaveable retains the UiState even on scroll and recomp.
Now just to sort out which specific elements are removed and shown after :D
The problem is that remember alone does not save values on configuration changes, whereas rememberSaveable does.
You can read more about this here.

How to mock geolocation in ARCore

I'm building a app that is using the geospatial API from ARCore. The indoor localization is obviously not very accurate, but to make the development process easier im trying to mock my device location.
Im able to create a mocked location with the system location manager.
fun setMockLocation(lat: Double, lng: Double, alt: Double) {
val provider = LocationManager.GPS_PROVIDER
val locationManager: LocationManager =
this.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.removeTestProvider(provider)
locationManager.addTestProvider(
provider,
false,
false,
false,
false,
false,
true,
true,
ProviderProperties.POWER_USAGE_LOW,
ProviderProperties.ACCURACY_FINE
)
val mockLocation = android.location.Location(provider)
mockLocation.latitude = lat
mockLocation.longitude = lng
mockLocation.altitude = alt
mockLocation.time = System.currentTimeMillis()
mockLocation.accuracy = 5.0f
mockLocation.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
locationManager.setTestProviderEnabled(provider, true)
locationManager.setTestProviderLocation(provider, mockLocation)
}
But it seems that the ARCore Earth is not using this location, because the obtained GeospatialPose is always different from the mocked location.
Any idea how I can mock the GeospatialPose obtained from ARCore? Or how to force ARCore to use the mocked location.

How I can remove\delete object in kotlin?

I have class and two objects. I want to delete 1st object. How I can delete it?
I tried just delete() (I found it on kotlinlangcom) but it doesn't work. I have red light bulb what recommend: "Create member function Person.delete", "Rename reference" and "Create extension function Person.delete".
fun main() {
// copy object in object
data class Person (var name: String = "Orig", var type: String = "piece",
var age: Int = 18, var high: Double = 25.7, var code: Int = 1522)
{
var info: String = "0"
get() = "Name: $name Age: $age Type: $type High: $high Code: $code"
}
val ann: Person = Person("Ann", "man", 10, 0.5, 1408) // 1st object with some properties
var bob: Person = Person("Bob", "girl", 20, 15.0, 1239) // 2nd object without prop
println(ann.info)// props 1st object
println(bob.info)// props 2nd object
print(" ---- ")
bob = ann.copy() // copy 1st in 2nd
println("Bob has Anns' props: ")
print("final " + bob.info) // new props 2nd object
bob.delete()
}
You don't need to thing about deleting objects like in other languages like c++/c ... the garbage collector of the JVM is taking care of it (if you use kotlin with jvm)
All you need to know is keeping no references on the object
So if you have a collection (list,map ...) where you put the object in, you also have to put it out if the collection is a property of a long living class like a model or something ... thats the only possiblity to getting into trouble within kotlin, putting a reference into a collection which is referenced by a static or long living object.
Within a function there is no need to delete the objects created withing.
Keep in mind that the garbage collector (GC) is not running instantly after finishing the method. There are different strategies depending on the age of the object and the garbage collector itself. If you would like to see the GC in action, this tool (visualgc) https://www.oracle.com/technetwork/java/visualgc-136680.html has some pretty nice visualisations.
You could also find much more details about garbage collection here: https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

Passing a tabelview indexPath.row as String to VC WebView

I have a TableViewController which I am attempting to pass through a URL to a WebView on another ViewController
I am overriding the below function, which works find if I make the URL static as you can see in the comment out let newsLink constant
let newsLink = "http://www.stuff.co.nz/business/industries/69108799/Kirkcaldie-Stains-department-store-to-become-David-Jones"
However with the below pulling the URL from indexPath.row it fails for some reason and passes through a nil value
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
let newsLink = (posts.objectAtIndex(indexPath.row).valueForKey("link") as! String)
//let newsLink = "http://www.stuff.co.nz/business/industries/69108799/Kirkcaldie-Stains-department-store-to-become-David-Jones"
println(newsLink)
let newsWebViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("idNewsWebViewController") as! NewsWebViewController
newsWebViewController.newsURL = NSURL(string: newsLink)
showDetailViewController(newsWebViewController, sender: self)
}
If I println() the below, I get exactly the same output as the URL I ahve hardcoded in the test let newsLink constant
println(posts.objectAtIndex(indexPath.row).valueForKey("link") as! String)
I can't figure out why this is failing. Hopefully someone smarter than me can help.
The code on the receiving end VC is below"
var newsURL : NSURL!
//var newsURL = NSURL(string: "http://www.google.co.nz")
#IBOutlet weak var newsWebView: UIWebView!
#IBOutlet weak var descTextView: UITextView!
and in the viewDidAppear function
let request : NSURLRequest = NSURLRequest(URL: newsURL!)
newsWebView.loadRequest(request)
More Info
var types
var posts = NSMutableArray()
var elements = NSMutableDictionary()
how I am adding objects
elements.setObject(urlLink, forKey: "link")
posts.addObject(elements)
Could you show the declaration / structure of the "posts" variable?
Without more information, the only thing I can think of is that the value of "link" is not actually a String, but something (maybe a NSURL) that when printed shows that content. That would explain the println showing the same url but the cast failing.
When you print, or implicitly convert any object to a String (as in the println), it calls the "description" method of that object.
For example:
class MyURLContainer {
var link:String
override func description() -> String {
return link
}
}
let url = MyURLContainer()
let url.link = "http://www.example.com"
println( "my link: \(url)" ) // this would show the link correctly
let link = url as? String // this will be nil, as url can't be casted to String
I think (posts.objectAtIndex(indexPath.row).valueForKey("link") does not return a String type. It might already be a NSURL, and hence showing you correct value in println()
Could you post some more details about it. Hope this helped.
It turns out it was the encoding on the URL that NSURL didn't like.
The solution was to use the below:
var escapedString = originalString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
I'm new to swift, but this seems a bit messy.

NSUserDefaults Not Saving TextField Text (Swift)

I'm trying to create a game with Swift, and I want to add the ability to create a username, which will be saved in NSUserDefaults. This is my code:
println("Textfield Text: \(usernameTextfield.text)")
NSUserDefaults.standardUserDefaults().setObject(usernameTextfield.text, forKey:"Username")
NSUserDefaults.standardUserDefaults().synchronize()
println(NSUserDefaults.standardUserDefaults().objectForKey("Username") as? String)
The output is:
Textfield Text: MyUsername
nil
The only explanation I can see as to why it is printing nil is that either the saving or the loading of the username is failing. Is there any way this can be corrected or am I doing something wrong?
Any help is appreciated!
println("Textfield Text: \(usernameTextfield.text)")
var myValue:NSString = usernameTextfield.text
NSUserDefaults.standardUserDefaults().setObject(myValue, forKey:"Username")
NSUserDefaults.standardUserDefaults().synchronize()
var myOutput: AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("Username")
println(myOutput)
In Swift 4.1
UserDefaults.standard.set(textfield.text, forKey: "yourKey") // saves text field text
UserDefaults.standard.synchronize()
// To Retrieve
textfield.text = UserDefaults.standard.value(forKey:"yourKey") as? String
I made a small modification to Roman's answer with Swift 2.0 and Xcode 6.4.
saving:
var myValue:NSString = usernameTF.text
NSUserDefaults.standardUserDefaults().setObject(myValue, forKey:"Username")
NSUserDefaults.standardUserDefaults().synchronize()
retrieving:
var myOutput = NSUserDefaults.standardUserDefaults().objectForKey("Username")
if (myOutput != nil)
{
self.title = "Welcome "+((myOutput) as! String)
}
In Swift 3.0
let userDefult = UserDefaults.standard //returns shared defaults object.
if let userName = usernameTextfield.text {
//storing string in UserDefaults
userDefult.set(userName, forKey: "userName") //Sets the value of the specified default key in the standard application domain.
}
print(userDefult.string(forKey: "userName")!)//Returns the string associated with the specified key.
For swift 3.0, You can create user default by,
UserDefaults.standard.set("yourValue", forKey: "YourString")
To Print the value in console :
print(UserDefaults.standard.string(forKey: "YourString")!)