How to cancel async image loading for uitableviewcells on new search - ios

I start a search on my server for profiles of users. As the user types usernames, different suggested profiles appear in a cell in a UITableView. Each profile has an image associated with it. The image is loaded asynchronously once the user types, via the cellForItem(at:) function. When the user types to search it cancels my search request to the server, however it also needs to cancel the asynchronous requests. There may be more than one request happening at a time - how should I keep track of them?
My code to download an image is like this:
model.downloadImage(
withURL: urlString,
completion: { [weak self] error, image in
guard let strongSelf = self else { return }
if error == nil, let theImage = image {
// Store the image in to our cache
strongSelf.model.imageCache[urlString] = theImage
// Update the cell with our image
if let cell = strongSelf.tableView.cellForRow(at: indexPath) as? MyTableCell {
cell.profileImage.image = theImage
}
} else {
print("Error downloading image: \(error?.localizedDescription)")
}
}
)
Note that I can add request = model.downloadImage(... if I wanted to, but request would only hold a pointer to the last time that the downloadImage() function was called.

For such purpose I recommend using Kingfisher. It's powerful 3rd party library for downloading and caching images. With kingfisher you can achieve what you want by simply calling:
// In table view delegate
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
//...
cell.imageView.kf.cancelDownloadTask()
}

You can use GCD, with iOS 8 & macOS 10.10 DispatchWorkItem was introduced, which provide this exact functionality (a task it can be canceled) in a very easy to use API.
This is an example:
class SearchViewController: UIViewController, UISearchBarDelegate {
// We keep track of the pending work item as a property
private var pendingRequestWorkItem: DispatchWorkItem?
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// Cancel the currently pending item
pendingRequestWorkItem?.cancel()
// Wrap our request in a work item
let requestWorkItem = DispatchWorkItem { [weak self] in
self?.model.downloadImage(...
}
// Save the new work item and execute it after 250 ms
pendingRequestWorkItem = requestWorkItem
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250),
execute: requestWorkItem)
}
}
In your case, you need to know through the delegate (example: TextField) when the user enters information.

Related

How to ensure the order of list shown in UITableView when getting data for cell from UIDocument

My app fetches data via FileManager for each cell in UITableView. The cells need data from a file which requires open UIDocument objects. However, it seems like code inside open completion handler get executed non predictably, so the cells don't get displayed in the order I wish.
How do I solve this problem? I appreciate if anyone gives any clue.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
fetchCellsData()
}
func fetchCellsData() {
do {
let files = getUrlsOfFiles() //files in a certain order
for file in files {
let document = MyDocument(fileURL: file) //MyDocument subclassing UIDocument
//open to get data for cell
document.open { (success) in
if !success {
print("File not open at %#", file)
return
}
let data = populateCellData(document.fileInfo)
self.cellsData.append(data)
self.tableView.reloadData()
/// tried below also
// DispatchQueue.main.async {
// self.cellsData.append(data)
// self.tableView.reloadData()
// }
}
document.close(completionHandler: nil)
}
} catch {
print(error)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCell
let fileInfo = cellsData[indexPath.row].fileInfo
//assign fileInfo into MyCell property
return cell
}
I expect cells get rendered in the order of 'files', however, it seems like the order is a bit unpredictable presuming that it's due to the fact that 'cellForRowAt' gets called before it knows about the full set of 'cellsData'.
From Apple documentation on UIdocument . Apple doc:
open(completionHandler:)
Opens a document asynchronously.
Which means that even if you trigger document.open in the right order, nothing guarantees that the completionHandler sequence will be in the same order, this is why the order is unpredictible.
However, you know that they will eventually all get done.
What you could do is :
1 - place all your datas from opened document into another list
2 - order this list in accordance to your need
3 - place this list into cellsData (which I assume is bound to your tableViesDatasource)
var allDatas: [/*your data type*/] = []
...
do {
// reset allDatas
allDatas = []
let files = getUrlsOfFiles()
for file in files {
let document = MyDocument(fileURL: file)
document.open { (success) in
if !success {
print("File not open at %#", file)
return
}
let data = populateCellData(document.fileInfo)
self.allDatas.append(data) // "buffer" list
self.tableView.reloadData()
}
document.close(completionHandler: nil)
}
} catch { ...
Then you change cellsData to a computed property like so :
var cellsData: [/*your data type*/] {
get {
return allDatas.order( /* order in accordance to your needs */ )
}
}
this way, each time a new data is added, the list is orderer before being redisplayed.
Discussion
this is the easier solution regarding the state of your code, however this may not be the overall prefered solution. For instance in your code, you reload your tableview each time you add a new value, knowing that there will be more data added after, which is not optimised.
I suggest you to read on Dispatch Group, this is a way to wait until all asynchronous operation your triggered are finished before executing certain actions (such as reloading your tableview in this case) (Readings: Raywenderlich tuts)

MutableProperty: execute method on value access

I'm using ReactiveSwift + SDWebImage to download/cache userAvatars of an API and then I display them in my ViewControllers.
I have multiple ViewControllers which want to display the userAvatar, then they listen to its async loading.
What is the best way for me to implement the flow described below?
The flow I would like to create here is:
ViewControllerA want to access userAvatar
it is the first time userAvatar is accessed then make an API request
ViewControllerA listens for userAvatar signals
ViewControllerA temporarily display a placeholder
ViewControllerB want to access userAvatar
ViewControllerB listens for userAvatar signals
ViewControllerB temporarily display a placeholder
API request of the userAvatar is completed, then send a signal observed by the viewcontrollers
viewcontrollers are refreshing their UIImageView with the fresh image
This is my actual code:
class ViewControllerA {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ... Cell creation
// type(of: user) == User.self (see class User below)
user.loadAvatarImage()
disposable = user.image.producer
.observe(on: UIScheduler())
.startWithValues { image in
// image is is either a placeholder or the real avatar
cell.userImage.image = image
}
}
}
class ViewControllerB {
override func viewDidLoad() {
super.viewDidLoad()
// type(of: user) == User.self (see class User below)
user.loadAvatarImage()
disposable = user.image.producer
.observe(on: UIScheduler())
.startWithValues { image in
// image is is either a placeholder or the real avatar
headerImageView.image = image
}
}
}
class User: Mappable {
// ... User implementation
let avatarImage = MutableProperty<UIImage?>(nil)
// To call before accessing avatarImage.value
func loadAvatarImage() {
getAvatar { image in
self.avatarImageProperty.value = image
}
}
private func getAvatar(completion: #escaping ((UIImage) -> Void)) {
// ... Async image download
competion(image)
}
}
I don't find that calling user.loadAvatarImage() before listening to the signal is very clean...
I know my code isn't so "Reactive", I still new with Reactive concept.
Feel free to criticize, I'm trying to improve myself
Thanks in advance for your advice.
The best way to handle this situation is to create a SignalProducer that:
if image is already downloaded when the SignalProducer is started: immediately emits .value(image) followed by .completed
if image is currently downloading when the SignalProducer is started: when image is finished downloading, emits .value(image) followed by .completed
if image has not been downloaded and is not currently downloading when the SignalProducer is started: initiates download of image, and when image is finished downloading emits .value(image) followed by .completed
ReactiveSwift provides us with a "manual" constructor for signal producers that allows us to write imperative code that runs every time the signal producer is started:
private let image = MutableProperty<UIImage?>(.none)
private var imageDownloadStarted = false
public func avatarImageSignalProducer() -> SignalProducer<UIImage, NoError> {
return SignalProducer { observer, lifetime in
//if image download hasn't started, start it now
if (!self.imageDownloadStarted) {
self.imageDownloadStarted = true
self.getAvatar { self.image = $0 }
}
//emit .value(image) followed by .completed when the image has downloaded, or immediately if it has already downloaded
self.image.producer //use our MutableProperty to get a signalproducer for the image download
.skipNil() //dont send the nil value while we wait for image to download
.take(first: 1) //send .completed after image value is sent
.startWithSignal { $0.observe(observer) } //propogate these self.image events to the avatarImageSignalProducer
}
}
To make your code even more "reactive" you can use the ReactiveCocoa library to bind your avatarImageSignalProducer to the UI:
ReactiveCocoa does not come with a BindingTarget for UIImageView.image built in, so we write an extension ourselves:
import ReactiveCocoa
extension Reactive where Base: UIImageView {
public var image: BindingTarget<UIImage> {
return makeBindingTarget { $0.image = $1 }
}
}
this lets us use the ReactiveCocoa binding operators in the ViewControllers to clean up our code in viewDidLoad/cellForRowAtIndexPath/etc like so:
class ViewControllerA {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ... Cell creation
cell.userImage <~ user.avatarImageSignalProducer()
.take(until: cell.reactive.prepareForReuse) //stop listening to signal & free memory when cell is reused before image loads
}
}
class ViewControllerB {
override func viewDidLoad() {
headerImageView.image <~ user.avatarImageSignalProducer()
.take(during: self.reactive.lifetime) //stop listening to signal & free memory when VC is deallocated before image loads
}
}
It is also important to think about memory & cyclical references when binding data to the UI that is not referenced in memory by the viewcontroller (e.g. if our User is a global variable that stays in memory after the VC is deallocated rather than a property of the VC). When this is the case we must explicitly stop listening to the signal when the VC is deallocated, or its memory will never be freed. The calls to .take(until: cell.reactive.prepareForReuse) and .take(during: self.reactive.lifetime) in the code above are both examples of explicitly stopping a signal for memory management purposes.

Kingfisher image render recycling cells

I'm using Kingfisher to download and cache my images. I'm using the custom class below CustomImageView that extends ImageView. I also am using my custom UITableViewCell to call the loadImage method within the didSet property in my CustomCell class file.
The below method loadImage is where all the magic happens.
class CustomImageView : UIImageView {
var lastUrlLoaded: String?
func loadImage(url:String) {
lastUrlLoaded = url
ImageCache.default.retrieveImage(forKey: url, options: nil) { (image, cacheType) in
if let image = image {
self.image = image
return
}
else {
guard let url = URL(string: url) else { return }
self.kf.setImage(with: url, placeholder: nil, options: nil, progressBlock: nil) {
(image, error, cacheTye, _) in
if let err = error {
self.kf.indicatorType = .none
DispatchQueue.main.async {
self.image = #imageLiteral(resourceName: "no_image_available")
}
print ("Error loading Image:", err)
return
}
if url.absoluteString != self.lastUrlLoaded { return }
if let image = image {
ImageCache.default.store(image, forKey: url.absoluteString)
DispatchQueue.main.async {
self.image = image
}
}
}
}
}
}
// Custom TableViewCell -- NewsCell.file
class NewsCell: UITableViewCell {
var article: Article? {
didSet{
guard let image_url = article?.image_url else { return }
_image.kf.indicatorType = .activity
_image.loadImage(url: image_url, type: "news")
}
}
}
// METHOD in my NewsController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! NewsCell
cell.article = articles[indexPath.item]
return cell
}
I'm having an issue when scrolling fast through my cells, some images that have loaded are being used for the wrong cells. I've researched and read other post but no answers helped me. I understanding it's dealing with the background task but just not sure how to fix it. If someone can help or point me in the right direction it would be much appreciated.
Solutions I've seen on stackoverflow and tried:
prepareForReuse in custom cell setting the image to nil.
setting the image to nil at cellforrowatindexpath.
override func prepareForReuse() {
super.prepareForReuse()
imageView.kf.cancelDownloadTask() // first, cancel currenct download task
imageView.kf.setImage(with: URL(string: "")) // second, prevent kingfisher from setting previous image
imageView.image = nil
}
Problem
When you scroll fast, you are reusing cells. The reused cells contain KF images which may have a running download task (initiated from the call to kf.setImage), for the image in the recycled article. If that task is allowed to finish, it will update your new cell with the old image. I will suggest two ways to fix it.
Answer 1 - Kill Recycled Download Tasks
Refer to the Kingfisher cheat sheet, Canceling a downloading task.
The cheat sheet shows how to stop those tasks in didEndDisplaying of your table view. I quote it here:
// In table view delegate
func tableView(_ tableView: UITableView, didEndDisplaying cell:
UITableViewCell, forRowAt indexPath: IndexPath) {
//...
cell.imageView.kf.cancelDownloadTask()
}
You could do it there or in the didSet observer in your cell class. You get the idea.
Answer 2 - Don't kill them, just ignore the result
The other approach would be to allow the download task to finish but only update the cell image if the downloaded URL matches the currently desired URL. Perhaps you were trying to do something like that with your lastUrlLoaded instance variable.
You have this in the completion handler of kf.setImage:
if url.absoluteString != self.lastUrlLoaded { return }
This is kind of what I mean, except you are not setting that variable properly to make it work. Let's imagine it was called "desiredURL" instead, for sake of explanation. Then you would set it in the didSet observer:
_image.desiredURL = image_url
And test it in the completion handler of kf.setImage:
// phew, finished downloading, is this still the image that I want?
if url != self.desiredURL { return }
Notes
If it were me, I would definitely go with the second approach (it has the positive side effect of caching the downloaded image, but also I don't trust canceling tasks).
Also, it seems that you are trying to do the hard way what kf.setImage does for you, i.e. loading the cache etc. I'm sure that you have your reasons for that, and either way you would still need to deal with this issue.
in your model check if the url not exist set it as nil, then in cell check your url if it is exist set it's image, else set a placeholder for that.
if article?.image_url != nil{
_image.kf.indicatorType = .activity
_image.loadImage(url: image_url, type: "news")
}else{
_image = UIImage(named: "placeholder")
}

image and label in interface builder overlap my data in the TableView cell

I am a beginner in iOS development, and I want to make an instagram clone app, and I have a problem when making the news feed of the instagram clone app.
So I am using Firebase to store the image and the database. after posting the image (uploading the data to Firebase), I want to populate the table view using the uploaded data from my firebase.
But when I run the app, the dummy image and label from my storyboard overlaps the downloaded data that I put in the table view. the data that I download will eventually show after I scroll down.
Here is the gif when I run the app:
http://g.recordit.co/iGIybD9Pur.gif
There are 3 users that show in the .gif
username (the dummy from the storyboard)
JokowiRI
MegawatiRI
After asynchronously downloading the image from Firebase (after the loading indicator is dismissed), I expect MegawatiRI will show on the top of the table, but the dummy will show up first, but after I scroll down and back to the top, MegawatiRI will eventually shows up.
I believe that MegawatiRI is successfully downloaded, but I don't know why the dummy image seems overlaping the actual data. I don't want the dummy to show when my app running.
Here is the screenshot of the prototype cell:
And here is the simplified codes of the table view controller:
class NewsFeedTableViewController: UITableViewController {
var currentUser : User!
var media = [Media]()
override func viewDidLoad() {
super.viewDidLoad()
tabBarController?.delegate = self
// to set the dynamic height of table view
tableView.estimatedRowHeight = StoryBoard.mediaCellDefaultHeight
tableView.rowHeight = UITableViewAutomaticDimension
// to erase the separator in the table view
tableView.separatorColor = UIColor.clear
}
override func viewWillAppear(_ animated: Bool) {
// check wheter the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
RealTimeDatabaseReference.users(uid: user.uid).reference().observeSingleEvent(of: .value, with: { (snapshot) in
if let userDict = snapshot.value as? [String:Any] {
self.currentUser = User(dictionary: userDict)
}
})
} else {
// user not logged in
self.performSegue(withIdentifier: StoryBoard.showWelcomeScreen, sender: nil)
}
}
tableView.reloadData()
fetchMedia()
}
func fetchMedia() {
SVProgressHUD.show()
Media.observeNewMedia { (mediaData) in
if !self.media.contains(mediaData) {
self.media.insert(mediaData, at: 0)
self.tableView.reloadData()
SVProgressHUD.dismiss()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: StoryBoard.mediaCell, for: indexPath) as! MediaTableViewCell
cell.currentUser = currentUser
cell.media = media[indexPath.section]
// to remove table view highlight style
cell.selectionStyle = .none
return cell
}
}
And here is the simplified code of the table view cell:
class MediaTableViewCell: UITableViewCell {
var currentUser: User!
var media: Media! {
didSet {
if currentUser != nil {
updateUI()
}
}
}
var cache = SAMCache.shared()
func updateUI () {
// check, if the image has already been downloaded and cached then just used the image, otherwise download from firebase storage
self.mediaImageView.image = nil
let cacheKey = "\(self.media.mediaUID))-postImage"
if let image = cache?.object(forKey: cacheKey) as? UIImage {
mediaImageView.image = image
} else {
media.downloadMediaImage { [weak self] (image, error) in
if error != nil {
print(error!)
}
if let image = image {
self?.mediaImageView.image = image
self?.cache?.setObject(image, forKey: cacheKey)
}
}
}
So what makes the dummy image overlaps my downloaded data?
Answer
The dummy images appear because your table view controller starts rendering cells before your current user is properly set on the tableViewController.
Thus, on the first call to cellForRowAtIndexPath, you probably have a nil currentUser in your controller, which gets passed to the cell. Hence the didSet property observer in your cell class does not call updateUI():
didSet {
if currentUser != nil {
updateUI()
}
}
Later, you reload the data and the current user has now been set, so things start to work as expected.
This line from your updateUI() should hide your dummy image. However, updateUI is not always being called as explained above:
self.mediaImageView.image = nil
I don't really see a reason why updateUI needs the current user to be not nil. So you could just eliminate the nil test in your didSet observer, and always call updateUI:
var media: Media! {
didSet {
updateUI()
}
Alternatively, you could rearrange your table view controller to actually wait for the current user to be set before loading the data source. The login-related code in your viewWillAppear has nested completion handers to set the current user. Those are likely executed asynchronously .. so you either have to wait for them to finish or deal with current user being nil.
Auth.auth etc {
// completes asynchronously, setting currentUser
}
// Unless you do something to wait, the rest starts IMMEDIATELY
// currentUser is not set yet
tableView.reloadData()
fetchMedia()
Other Notes
(1) I think it would be good form to reload the cell (using reloadRows) when the image downloads and has been inserted into your shared cache. You can refer to the answers in this question to see how an asynch task initiated from a cell can contact the tableViewController using NotificationCenter or delegation.
(2) I suspect that your image download tasks currently are running in the main thread, which is probably not what you intended. When you fix that, you will need to switch back to the main thread to either update the image (as you are doing now) or reload the row (as I recommend above).
Update your UI in main thread.
if let image = image {
DispatchQueue.main.async {
self?.mediaImageView.image = image
}
self?.cache?.setObject(image, forKey: cacheKey)
}

How do I async load a text file from URL, then async load images from URLs in the text file? (in swift)

I'm new to iOS development, so any help would be appreciated.
The project: I am developing an Emoji-style keyboard for iOS. When the keyboard first loads it downloads a .txt file from the Internet with image URLs (this way I can add/delete/reorder the images simply by updating that .txt file). The image URLs are entered into an Array, then the array of image URLs is used to populate a collection view.
The code I have written works, but when I switch to my keyboard the system waits until everything is loaded (typically 2-3 seconds) before the keyboard pops up. What I want is for the keyboard to popup instantly without images, then load the images as they become available - this way the user can see the keyboard working.
How can I get the keyboard to load instantly, then work on downloading the text file followed by the images? I am finding a lot of information on asynchronous downloading for images, but my problem is a little more complex with the addition of the text download and I can't quite figure it out.
more info:
I'm using SDWebImage to load the images so they are caching nicely and should be loading asynchronously, but the way I have it wrapped up the keyboard is waiting until everything is downloaded to display.
I tried wrapping the "do { } catch { }" portion of the code below inside - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { //do-catch code }) - when I do this the keyboard pops up instantly like I want, but instead of 2-3 seconds to load the images, it takes 8-10 seconds and it still waits for all images before showing any images.
Below is my code. I simplified the code by removing buttons and other code that is working. There are two .xib files associated with this code: KeyboardView and ImageCollectionViewCell
import UIKit
import MobileCoreServices
import SDWebImage
class ImageCollectionViewCell:UICollectionViewCell {
#IBOutlet var imgView:UIImageView!
}
class KeyboardViewController: UIInputViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var collectionView: UICollectionView!
let blueMojiUrl = NSURL(string: "http://example.com/list/BlueMojiList.txt")
var blueMojiArray = NSMutableArray()
func isOpenAccessGranted() -> Bool {
// Function to test if Open Access is granted
return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
}
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "KeyboardView", bundle: nil)
let objects = nib.instantiateWithOwner(self, options: nil)
self.view = objects[0] as! UIView;
self.collectionView.registerNib(UINib(nibName: "ImageCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "ImageCollectionViewCell")
if (isOpenAccessGranted()) == false {
// Open Access is NOT granted - display error instructions
} else {
// Open Access IS granted
do {
self.blueMojiArray = NSMutableArray(array: try NSString(contentsOfURL: self.blueMojiUrl!, usedEncoding: nil).componentsSeparatedByString("\n"));
self.collectionView.reloadData()
} catch {
// Error connecting to server - display error instructions
}
}
} // End ViewDidLoad
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.blueMojiArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let iCell = collectionView.dequeueReusableCellWithReuseIdentifier("ImageCollectionViewCell", forIndexPath: indexPath) as! ImageCollectionViewCell
let url = NSURL(string: self.blueMojiArray.objectAtIndex(indexPath.row) as! String)
iCell.imgView.sd_setImageWithURL(url, placeholderImage: UIImage(named: "loading"))
iCell.layer.cornerRadius = 10.0
return iCell
}
}
UPDATE:
Thanks for the answers. I was able to fix my problem with Alamofire. I installed the Alamofire pod to my project. The specific code I used to fix my issue: first I added import Alamofire at the top, then I removed the do { } catch { } code above and replaced it with
Alamofire.request(.GET, blueMojiUrl!)
.validate()
.responseString { response in
switch response.result {
case .Success:
let contents = try? NSString(contentsOfURL: self.blueMojiUrl!, usedEncoding: nil)
self.blueMojiArray = NSMutableArray(array: contents!.componentsSeparatedByString("\n"))
self.collectionView.reloadData()
case .Failure:
// Error connecting to server - display error instructions
}
}
Now the keyboard loads instantly, followed by the images loading within 2-3 seconds.
I would do something like that:
On cellForItemAtIndexPath call method to download image for current cell with completion handler. On download completion set cell's image to downloaded image and call method reloadItemsAtIndexPaths for this cell.
Example code:
func downloadPlaceholderImage(url: String, completionHandler: (image: UIImage) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
/// download logic
completionHandler(image: downloadedImage)
})
}
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
///
downloadPlaceHolderImage("url", completionHandler: { image in
cell.image = image
self.collectionView.reloadItemsAtIndexPaths([indexPath])
})
///
}
The recommend way to do this after iOS 8 is with NSURLSession.dataTaskWithURL
See:
sendAsynchronousRequest was deprecated in iOS 9, How to alter code to fix

Resources