So, I've been working on a Weather App with the following brief Data Model
class CurrentWeather
{
private var _cityName: String!
private var _date: String!
private var _weatherType: String!
private var _currentTemp: Double!
var cityName: String
{
if _cityName == nil
{
_cityName = ""
}
return _cityName
}
// Same idea for getters var date, var weatherType and
// var currentTemp (returns 0.0 if it is nil)
// Not showing that here
func downloadWeatherDetails(completed: DownloadComplete)
{
// Function which computes values though a url and stores in instance variables
// Not showing the entire actual function here
self._cityName = name.capitalized. // value computed earlier
print(self._cityName)
self._weatherType = main.capitalized // value computed earlier
print(self._weatherType)
self._currentTemp = currentTemp - 273.15 // value computed earlier
print(self._currentTemp)
completed()
}
}
where the type DownloadComplete is a type alias to ()->()
In the main ViewController.swift, I have created an object and called this function (with trailing closure syntax)
var currentWeather: CurrentWeather!
override func viewDidLoad()
{
super.viewDidLoad()
currentWeather = CurrentWeather()
currentWeather.downloadWeatherDetails {
self.updateMainUI() // I have created this function
}
}
func updateMainUI()
{
dateLabel.text = currentWeather.date
currentTempLabel.text = String(currentWeather.currentTemp)
locationLabel.text = currentWeather.cityName
currentWeatherTypeLabel.text = currentWeather.weatherType
currentWeatherImage.image = UIImage(named: currentWeather.weatherType)
print("Tested: \(currentWeather.currentTemp)")
print("Tested: \(currentWeather.cityName)")
print("Tested: \(currentWeather.weatherType)")
}
So the Expected output:
Logically,
I have created a CurrentWeather object
Called the downloadWeatherDetails function which should load the different computed values in the private vars.
Call the user defined updateMainUI function which displays the different values on my app's UI
So the output should be like
Birim. //cityname
Clear. //weatherType
29.134 //currentTemp
Tested: 29.134
Tested: Birim
Tested: Clear
But the output which I get is
Tested: 0.0
Tested: (indicating "")
Tested: (indicating "")
Birim
Clear
29.134
So, basically the functions downloadWeatherDetails and updateMainUI are called in the wrong order? Why is this so? Is this somehow related to asynchronous execution of functions?
I have tried not using the trailing closure, but it still doesn't work.
I also tried leaving the closure empty and calling updateMainUI after downloadWeatherDetails call like this
currentWeather.downloadWeatherDetails {
}
self.updateMainUI()
But this too doesn't work. Any ideas of why the functions are called in the wrong order?
UPDATE:
the underscore variables are private vars while the non-underscore variables are the getters like
var cityName: String
{
if _cityName == nil
{
_cityName = ""
}
return _cityName
}
// Same idea for getters var date, var weatherType and
// var currentTemp (returns 0.0 if it is nil)
// Not showing that here
UPDATE 2:
Project files are here(in case one may want to refer): https://github.com/danny311296/Weather-App
You must call your 'completed()' function within the Alamofire request callback. Since the request function is asynchronous it does not wait for it to finish before executing completed().
Alamofire.request(CURRENT_WEATHER_URL).responseJSON { response in
// handle response...
// when done call completed
completed()
}
I guess the issue is that your "updateMainUI" method is being called before the completion of download process. Although you have implemented the Completion Listener, this should be working but I don't what's wrong with that. Try to use some other methods like delegation or notification to observe the downloading processes.
Check this link to see other method to observe completion:
https://medium.com/ios-os-x-development/ios-three-ways-to-pass-data-from-model-to-controller-b47cc72a4336
Related
This question already has answers here:
Different process between Struct and Class in mutating asynchronously in Swift3
(2 answers)
Closed last year.
I have the following code I'm using with SwiftUI:
import Foundation
public struct Trigger {
public var value = false
public mutating func toggle() {
value = true
let responseDate = Date().advanced(by: 3)
OperationQueue.main.schedule(after: .init(responseDate)) {
moveBack()
}
}
private mutating func moveBack() {
value = false
}
}
However, I'm getting an error:
Escaping closure captures mutating 'self' parameter
I understand that changing the struct to a class would solve this issue, but is there any way to actually capture a mutating self in an escaping closure in a struct?
As you have found, the quick solution is to use a reference type, a class. But why is this the case?
Swift structs are value types, so they are immutable. You can mark a function as mutating to indicate to the compiler that a function mutates the struct, but what does that actually mean?
Consider a simple struct:
struct Counter {
var count
init(_ count: Int = 0)
{
self.count = count
}
mutating func increment() {
self.count+=1
}
}
Now, try and assign an instance of this to a let constant:
let someCounter = Counter()
someCounter.increment()
print(someCounter.count)
You will get an error; you need to use a var.
var someCounter = Counter()
someCounter.increment()
print(someCounter.count)
What actually happens when you call a mutating func is that a new Counter is created, with the new count and it is assigned to someCounter. It is effectively saying someCounter = Counter(someCounter.count+1)
Now, think what would happen if you could mutate self in an escaping closure - That new Counter is going to be created at some unspecified time in the future, but execution has already moved on. It is too late to update someCounter.
The other advantage of using a class, as you have found, is that you can use ObservableObject, which makes updating your SwiftUI views much easier.
Solution I finished with:
import Foundation
import Combine
public final class IntervalTrigger: ObservableObject {
private let timeInterval: TimeInterval
#Published var value = false
public init(_ timeInterval: TimeInterval) {
self.timeInterval = timeInterval
}
public func toggle() {
value = true
let responseDate = Date().advanced(by: timeInterval)
OperationQueue.main.schedule(after: .init(responseDate)) { [weak self] in
self?.value = false
}
}
}
My goal is to use a button (that contains multiple messages) to trigger a text (making a marker such as first click will be method 1, second click will be method 2) correspondingly added at the end of the my data (after joined(separator: "~")) so that it could help me to analyze which button was clicked when I look back at the data.
Currently, I have a struct that will output the data:
struct CaptureData {
var vertices: [SIMD3<Float>] //A vector of three scalar values. It will return a list of [SIMD3<Float>(x,y,z)]
var mode: Mode = .one
mutating func nextCase() { // the data method will be changed
mode = mode.next()
}
var verticesFormatted : String { //I formatted in such a way so that it can be read more clearly without SIMD3
let v = "<" + vertices.map{ "\($0.x):\($0.y):\($0.z)" }.joined(separator: "~") + "trial: \(mode.next().rawValue)"
return "\(v)"
}
}
Based on #Joshua suggestion
enum Mode: String, CaseIterable {
case one, two, three
}
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
func next() -> Self {
return self.nextCase
}
}
And the button is alternating the messages after each click,
var x = 0
var instance = CaptureData(vertices: [SIMD3<Float>])
// Next button for changing methods
#IBAction func ChangingTapped(_ btn: UIButton) {
if(x==0){
Textfield.text = "changing to driving"
}
else if(x==1){
Textfield.text = "changing to walking"
instance.nextCase()
}
else{
Textfield.text = "changing to cycling"
instance.nextCase()
}
x += 1
}
Updates: I am able to print one of the methods , .two (method two), after separator: "~". However, at the moment I am still not be able to click button to switch the case in the data.
The main problem is the initialization of variables. I am not able to define var instance = CaptureData(vertices: [SIMD3<Float>]) because it comes with error: Cannot convert value of type '[SIMD3<Float>].Type' to expected argument type '[SIMD3<Float>]'
I am sorry if my explanation is a bit messy here. I am trying to describe the problem I have here. Let me know if there is anything missing! Thank you so much in advance.
Enums is a data type that is more like a constant but much more readable.
An example will be passing in a status to a function.
enum Status {
case success
case failure
}
func updateStatus(_ status: Status) {
statusProperty = status
}
// somewhere in your code
instance.updateStatus(.success)
versus using an Int as a value.
func updateStatus(_ status: Int) {
statusProperty = status
}
// somewhere in your code
instance.updateStatus(1) // eventually you'll forget what this and you'll declare more of a global variable acting as constant, which technically what enums are for.
Enums in swift are a bit different though, much more powerful. More info about enums here
Back to the topic.
enum Mode: String, CaseIterable {
case one, two, three
}
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
func next() -> Self { // you don't need to update self here, remember self here is one of the items in the enum, i.e. one, so assigning one = two just doesn't work.
return self.nextCase
}
}
// The data struct
struct CaptureData {
var mode: Mode = .one
// we add a mutation function here so we can update the mode
mutating func nextCase() { // the data your concern about, that can actually mutate is the mode property inside CaptureData struct.
mode = mode.next()
}
}
So lets say somewhere in the app you can use it like this you initialised an instance of CaptureData:
var instance = CaptureData() // Don't forget it should be var and not let, as we are updating its property.
instance.nextCase() // get the next case, initially it was .one
print(instance.mode) // prints two
instance.nextCase() // from .two, changes to .three
print(instance.mode) // prints three
Hope this helps.
This is the class where the value is
class CurrentWeather{
var _date:String!
var _cityName:String!
var _temp:Double!
var _weatherType:String!
var cityName:String{
if _cityName==nil{
_cityName = ""
}
return _cityName
}
var currentTemprature:Double{
if _temp==nil{
_temp = 0.0
}
return self._temp
}
var weathertype:String{
if _weatherType==nil{
_weatherType = ""
}
return _weatherType
}
var date:String{
if _date==nil{
_date = ""
}
let dateFormater=DateFormatter()
dateFormater.dateStyle = .long
dateFormater.timeStyle = .none
let currentDate = dateFormater.string(from: Date())
self._date="\(currentDate)"
return _date
}
func weatherDataDownload(completed : downloadComplete){
let weatherUrl=URL(string: constant)!
Alamofire.request(weatherUrl , method:.get).responseJSON{response in
if let dict=response.result.value as? Dictionary<String,AnyObject>{
if let name=dict["name"] as? String{
self._cityName = name.capitalized
print(name.capitalized)
}
if let weather=dict["weather"] as? [Dictionary<String,AnyObject>]{
if let main=weather[0]["main"] as? String{
self._weatherType=main.capitalized
print(main.capitalized)
}
}
if let tempr=dict["main"] as? Dictionary<String,AnyObject>{
if let temp=tempr["temp"] as? Double{
let convertedTemp=Double(round(temp-273.15))
self._temp=convertedTemp
print(convertedTemp)
}
}
}
}
completed()
}}
This is the ViewController class
var currentWeatherOj = CurrentWeather()
override func viewDidLoad() {
super.viewDidLoad()
table.delegate=self
table.dataSource=self
currentWeatherOj.weatherDataDownload {
self.updateUIweather()
}
}
func updateUIweather () {
weatherType.text=currentWeatherOj.weathertype
presentDate.text=currentWeatherOj.date
presentLocation.text=currentWeatherOj.cityName
presentTemp.text="\(currentWeatherOj.currentTemprature)"
}
when I try to call in ViewController its showing the default value which I set inside of computed variable other than _date but I am able print values inside the func of weatherDataDownload.I am confused how variables in swift 3 works because of this.
See the comments in the following code sample. You need to move the call to "completed()"
func weatherDataDownload(#escaping completed : downloadComplete) {
let weatherUrl=URL(string: constant)!
Alamofire.request(weatherUrl , method:.get).responseJSON { response in
// ... leave your code here alone
// put the call to completed() here
completed()
}
// not here
}
When you make the all to Alamofire, it executes it's request on a background thread. When that request completes, it calls the closure that you've defined (the one that starts "response in..."). You don't want to call updateUIweather until that has been done so you put the call to "completed()" inside of the same completion handler.
When the call to completed was outside of that completion handler, it would be called right away... immediately after the Alamofire request was sent (but before it had finished on that background thread). None of the code in the completion handler has run yet so your variables aren't updated yet.
Finally because your completed closure was passed to a block that was then sent off to a background thread, that closure "escapes" the current function. You add the #escaping so that folks reading your code will know that the closure will live on beyond the life of that function.
Is it possible to observe values being added to a Set data-structure?
What i'm trying to achieve:
var storedStrings = Set<String>() {
didSet (value) {
// where value is a string that has been added to the Set
}
}
Example:
storedStrings.insert("hello")
didSet called, as a new value has been added.
storedString.insert("world")
didSet called again.
storedString.insert("hello")
didSet not called, as the set already contains the string "hello"
This can be a bit expensive, but you still can do something like:
var storedStrings = Set<String>() {
didSet {
if storedStrings != oldValue {
print("storedStrings has changed")
let added = storedStrings.subtracting(oldValue)
print("added values: \(added)")
let removed = oldValue.subtracting(storedStrings)
print("removed values: \(removed)")
}
}
}
The insert function returns a tuple with the definition: (inserted: Bool, memberAfterInsert: Element).
Therefore the check for a new unique element can be made on insertion instead of using didSet.
var storedStrings = Set<String>()
var insertionResult = storedStrings.insert("Hello")
if insertionResult.inserted {
print("Value inserted") // this is called
}
insertionResult = storedStrings.insert("Hello")
if insertionResult.inserted {
print("Value inserted") // this isn't called
}
You could implement your own inserter for your set, which could emulate the use of a property observer, making use of the fact that the insert method of Set returns a tuple whose first member is false in case the element to be inserted is already present.
func insert(Element)
Inserts the given element in the set if it is not already present.
From the language reference.
E.g.:
struct Foo {
private var storedStrings = Set<String>()
mutating func insertNewStoredString(_ newString: String) {
if storedStrings.insert(newString).0 {
print("Inserted '\(newString)' into storedStrings")
}
}
}
var foo = Foo()
foo.insertNewStoredString("hello") // Inserted 'hello' into storedStrings
foo.insertNewStoredString("hello")
foo.insertNewStoredString("world") // Inserted 'world' into storedStrings
What is the best practice to check if an array of objects has been loaded in Swift?
Say, if I declare an array in a class, and it is lazily loaded. Apple's docs say the array declaration / initialization is something like
var events = [Event]()
I suppose the above means the array is already initialized (ie. not nil).
Then, I need a function like:
func getEvents() -> [Event] {
// check if array is nil, and if so, load the events (not: which could be 0 events)
return events
}
In Java, I would declare something like
ArrayList<Event> events;
public ArrayList<Event> getEvents() {
if(!events) { // null means never been loaded
events = new ArrayList<Event>();
events = loadEvents(); // load the events, which could be zero
}
}
What is the best practice to code the equivalent in Swift?
Lazy Stored Property
In Swift you can declare a lazy stored property: it does exactly what you need.
struct Event { }
class Foo {
lazy var events: [Event] = { self.loadEvents() }()
private func loadEvents() -> [Event] {
print("Loading events")
return [Event(), Event(), Event()]
}
}
We associated a closure to the events property. The closure defines how the events property should be populated and will be executed only when the property is used.
let foo = Foo()
print(foo.events) // now the events property is populated
You may experience an Array in these ways:
var vect = [String]()
if vect.isEmpty {
print ("true")
}else{
print("false")
}
Or
func ceck()->Void{
guard !vect.isEmpty else{
print ("true")
return // return, break, or another logic this is example
}
}