Multithreading in Swift 3 with GCD - multithreading

I'm working on a webservice based app and I've come to a crashing hault due to sending requests too quickly to the webservice. I simply can not getting GCD working in Swift 3 and I'm scratching my head. I've decided to dumb it down and just try loading 4 web images to a web view in order. Based on everything I'm seeing online the following code should work, but it is still freezing the UI until all four images load. What am I doing wrong?
import UIKit
let imageURLs = ["http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg", "http://adriatic-lines.com/wp-content/uploads/2015/04/canal-of-Venice.jpg", "http://hd-wall-papers.com/images/wallpapers/hi-resolution-pictures/hi-resolution-pictures-5.jpg", "http://hd-wall-papers.com/images/wallpapers/hi-resolution-pictures/hi-resolution-pictures-1.jpg"]
class Downloader {
class func downloadImageWithURL(_ url:String) -> UIImage! {
let data = try? Data(contentsOf: URL(string: url)!)
return UIImage(data: data!)
}
}
class ViewController: UIViewController {
#IBOutlet weak var imageView1: UIImageView!
#IBOutlet weak var imageView2: UIImageView!
#IBOutlet weak var imageView3: UIImageView!
#IBOutlet weak var imageView4: UIImageView!
#IBOutlet weak var sliderValueLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func didClickOnStart(_ sender: AnyObject) {
let serialQueue = DispatchQueue(label: "syncQueue")
serialQueue.sync{
let img1 = Downloader.downloadImageWithURL(imageURLs[0])
DispatchQueue.main.async(execute: {
self.imageView1.image = img1
})
}
serialQueue.sync{
let img2 = Downloader.downloadImageWithURL(imageURLs[1])
DispatchQueue.main.async(execute: {
self.imageView2.image = img2
})
}
serialQueue.sync{
let img3 = Downloader.downloadImageWithURL(imageURLs[2])
DispatchQueue.main.async(execute: {
self.imageView3.image = img3
})
}
serialQueue.sync{
let img4 = Downloader.downloadImageWithURL(imageURLs[3])
DispatchQueue.main.async(execute: {
self.imageView4.image = img4
})
}
}
#IBAction func sliderValueChanged(_ sender: UISlider) {
self.sliderValueLabel.text = "\(sender.value * 100.0)"
}
}

freezing the UI
Because you are calling serialQueue.sync. You almost never want to call sync, and in this case you certainly don't. Use async instead.

Related

Use RxSwift Driver cause memory leak

I write a demo about this leak.
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
private let viewModel = ViewModel()
private let disposeBag = DisposeBag()
private lazy var nameLabel: UILabel = {
let label = UILabel()
label.text = "idle"
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(nameLabel)
nameLabel.frame = CGRect(x: 100, y: 100, width: 100, height: 30)
// not leak
viewModel.name.asObservable().bind(to: nameLabel.rx.text).disposed(by: disposeBag)
// leak
// viewModel.name.asDriver().drive(onNext: { [weak self] (name) in
// guard let weakSelf = self else { return }
// weakSelf.nameLabel.text = name
// }).disposed(by: disposeBag)
// leak
// viewModel.name.asDriver().drive(nameLabel.rx.text).disposed(by: disposeBag)
viewModel.name.accept("act")
}
}
class ViewModel {
let name: BehaviorRelay<String> = BehaviorRelay(value: "")
}
In Xcode Instrument Leaks, it will report leaks when using driver. But change to "bindTo", it works fine and no leaks report.
I change "driver.driveTo" to driver.drive(onNext), and handle recycle problem, still report leaks.
I'm really confused about this. And I wish someone can tell me why this happened, how can I fix it, or avoid this kind of leaks in the future.
Thanks.

Converting from UITextField to Int failure

I cannot convert the UITextField to an Int, although I have taken a look at the instructions on the internet. I'm a beginner with Swift 2. Can anyone help?
import UIKit
class ViewController: UIViewController {
#IBOutlet var age: UITextField!
#IBOutlet var resultLabel: UILabel!
#IBAction func findAge(_ sender: AnyObject) {
// Changed "enteredAge = age" to "enteredAge = age!"
var age = Int = Int(age.text)
var enteredAge = Int(age)
if enteredAge != nil {
var catYears = enteredAge! * 7
resultLabel.text = "Your cat is \(catYears) in cat years"
} else {
resultLabel.text = "Please enter a number in the box"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Entered age use age.text again as in age variable
As I see in your code you are redeclaring the age textfield , I am editing you IBAction . I have not run this code , but I think It will work for you . Just copy paste in your code any check it .
#IBAction func findAge(_ sender: AnyObject) {
var ageToInt = Int(self.age.text!)!
if ageToInt != nil {
var catYears = ageToInt! * 7
resultLabel.text = "Your cat is \(catYears) in cat years"
} else {
resultLabel.text = "Please enter a number in the box"
}
}
Good luck!

Different info for different pin annotations

I have two different pins placed on my mapview. I have an info button on each. The info buttons will segue to the a UIViewController that has a Image view (to hold a picture of the place) and a Text label ( To hold info about the place).
My problem is how can I generate the Info and picture depending on which pin annotation button was selected. The last function is the one used in order to segue to the info view controller.
class GetToTheStart: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
//map view outlet
#IBOutlet weak var mapView: MKMapView!
//defining use of location manager
let myLocMgr = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
//setting up location request
myLocMgr.desiredAccuracy = kCLLocationAccuracyBest
myLocMgr.requestWhenInUseAuthorization()
myLocMgr.startUpdatingLocation()
myLocMgr.delegate = self
mapView.delegate = self
// coordinates of desired locations for pins
var zoo1 = CLLocationCoordinate2DMake(53.347439, -6.291820)
var town1 = CLLocationCoordinate2DMake(53.347247, -6.290865)
//setting up pin 1 annotation (the zoo)
var zoopin = MKPointAnnotation()
zoopin.coordinate = zoo1
zoopin.title = "Dublin Zoo"
zoopin.subtitle = "This this the zoo"
mapView.addAnnotation(zoopin)
//setting up pin 2 annotation (the town)
var townpin = MKPointAnnotation()
townpin.coordinate = zoo1
townpin.title = "Dublin town"
townpin.subtitle = "This this the town"
mapView.addAnnotation(townpin)
}
//setting up Pin callout button for segue
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
}
let reuseIdentifier = "pin"
var pin = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseIdentifier) as? MKPinAnnotationView
if pin == nil {
pin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
pin!.pinColor = .Red
pin!.canShowCallout = true
pin!.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
} else {
pin!.annotation = annotation
}
return pin
}
//performing segue from info button to infoViewController
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
performSegueWithIdentifier("info", sender: view)
}
For this you need to override below method. Here we will get the annotationView which will trigger the segue.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "info") {
if let annotation = sender as? MKAnnotationView {
let detailViewController = segue.destinationViewController as! DetailViewController
detailViewController.titleText = annotation.annotation?.title ?? ""
detailViewController.detaileText = annotation.annotation?.subtitle ?? ""
}
}
}
And in the detailViewController is same as your infoViewController and here I have two labels and for that I have two public variables. This is just to avoid error because at this point we don't have the label objects.
Here is the code for my DetailViewController.
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var detailLabel: UILabel!
var titleText: String? { didSet { updateUI() } }
var detaileText: String? { didSet { updateUI() } }
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
private func updateUI() {
self.titleLabel?.text = self.titleText
self.detailLabel?.text = self.detaileText
}
}

How do you show a blue dot instead of a Pin when showing current location in mapview?

How do you show a blue dot instead of a Pin when showing current location in map view? at the moment the code illustrates a red pin that shows the users current location as the user moves around. How can i convert this to the blue dot that apple use?
import UIKit
import MapKit
class ViewController: UIViewController,CLLocationManagerDelegate {
#IBOutlet weak var myMapView: MKMapView!
let myLocMgr = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
myLocMgr.desiredAccuracy = kCLLocationAccuracyBest
myLocMgr.requestWhenInUseAuthorization()
myLocMgr.startUpdatingLocation()
myLocMgr.delegate = self
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// get most recient coordinate
let myCoor = locations[locations.count - 1]
//get lat & long
let myLat = myCoor.coordinate.latitude
let myLong = myCoor.coordinate.longitude
let myCoor2D = CLLocationCoordinate2D(latitude: myLat, longitude: myLong)
//set span
let myLatDelta = 0.05
let myLongDelta = 0.05
let mySpan = MKCoordinateSpan(latitudeDelta: myLatDelta, longitudeDelta: myLongDelta)
let myRegion = MKCoordinateRegion(center: myCoor2D, span: mySpan)
//center map at this region
myMapView.setRegion(myRegion, animated: true)
//add anotation
let myAnno = MKPointAnnotation()
myAnno.coordinate = myCoor2D
myMapView.addAnnotation(myAnno)
}
#IBAction func stop(sender: AnyObject) {
myLocMgr.stopUpdatingLocation()
}
#IBAction func resume(sender: AnyObject) {
myLocMgr.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
self.myMapView.showsUserLocation = true
showsUserLocation is what you need. It's MKMapView property set it in viewDidLoad or using IB (if possible). You don't need to do any extra stuff in LocationManager's delegate didUpdateLocations, just set it the MKMapView will do the rest of the stuff

Can't Unwrap Optional.None when using CoreData in Swift

Ok so the problem actually occurs once the code bit var context: NSManagedObjectContext = appDel.managedObjectContext is run I commented it out to confirm that it was that line and it was please note this is my first time learning iOS programming so please try to be as specific as possible in your answer thank you :)
import UIKit
import CoreData
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var txtName : UITextField
#IBOutlet var txtDesc : UITextField
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
self.view.endEditing(true)
}
#IBAction func hitAdd(sender : UIButton) {
glTask.newTask(txtName.text, desc: txtDesc.text)
txtName.text = ""
txtDesc.text = ""
self.view.endEditing(true)
self.tabBarController.selectedIndex = 0
var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
Right here V
var context: NSManagedObjectContext = appDel.managedObjectContext
This crashes the app once button is pressed ^
The code error message is fatal error Cant unwrap Optional.None
var newTask = NSEntityDescription.insertNewObjectForEntityForName("Tasks", inManagedObjectContext: context) as NSManagedObject
newTask.setValue("test task", forKey: "myTask")
newTask.setValue("test Description", forKey: "myDesc")
context.save(nil)
//println(newTask)
println("Task was saved.")
}
// UITextField Delegate
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder()
return true
}
}
Looking at the Core Data stack in Swift, managedObjectContext is implemented like this:
var managedObjectContext: NSManagedObjectContext {
if !_managedObjectContext {
let coordinator = self.persistentStoreCoordinator
if coordinator != nil {
_managedObjectContext = NSManagedObjectContext()
_managedObjectContext!.persistentStoreCoordinator = coordinator
}
}
return _managedObjectContext!
}
var _managedObjectContext: NSManagedObjectContext? = nil
As you can see it is backed by an Optional.
The place where this can go wrong is here:
_managedObjectContext = NSManagedObjectContext()
_managedObjectContext!.persistentStoreCoordinator = coordinator
if NSManagedObjectContext() returns a nil, then the backing _managedObjectContext will be nil and you will get this crash at the line where you unwrap it return _managedObjectContext!
To debug this, dig deeper down the stack, its most likely failing to initialize the object model or persistant store, and thus returning nil to you.
Edit:
In the definiton of the getter for var persistentStoreCoordinator: NSPersistentStoreCoordinator
They provide a location (the wall of comments) where you should debug this exact type of issue.
Not sure if OP ever figured this out, but I had a similar issue and realized that the code I copied from another app's AppDelegate was using the project name of that app and that I had forgot to change this line: let modelURL = NSBundle.mainBundle().URLForResource("CoreData", withExtension: "momd") to use "CoreData" instead of the "test" it had from another project.

Resources