Make a simple popup window in iOS SDK (Swift)

I was asked to create a simple popup window on a project I’m currently working on and I haven’t found any nice implementation of a clean popup window with the iOS SDK.

import UIKit

class PopUpViewControllerSwift : UIViewController {
 
 @IBOutlet weak var popUpView: UIView!
 @IBOutlet weak var messageLabel: UILabel!
 @IBOutlet weak var logoImg: UIImageView!
 
 override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
 super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
 }

 required init?(coder aDecoder: NSCoder) {
 super.init(coder: aDecoder)
 }
 
 override func viewDidLoad() {
 super.viewDidLoad()
 self.view.frame = CGRectMake(0, 0, UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height) //sets the
 self.view.backgroundColor = UIColor.clearColor()
 
 self.popUpView.layer.cornerRadius = 5
 self.popUpView.layer.shadowOpacity = 0.8
 self.popUpView.layer.shadowOffset = CGSizeMake(0.0, 0.0)
 }
 
 @IBAction func closePopup(sender: AnyObject) {
 self.dismissViewControllerAnimated(true, completion: nil)
 }
}
Advertisements

Create a auto hide popup with swift

Mar 05, 2015 19:401

So i needed a popup kind of view for an IOS app. The purpose of the popup was to display a message when a gesture action has taken place. Gestures can be confusing for people and for complex gesture its better to show some kind of message so that user knows that something happened. Here is the code.

1) Take a ViewController. Add a button and a UIVIew on it. In the UIView add a label. This label can be changed based on the message that needs to be passed.
Screenshot at Mar 05 19-44-25

2) In the view controller we add our code.  Code has 2 methods.  On button click i am displaying the popup and triggering a timer.  2nd method gets called when the timer is over. In the 2nd method i hide the popup with animation.

 

Showing a popup window in iOS ( +class for download)

Hi this is Marin – the author of Touch Code Magazine, I hope you are enjoying my tutorials and articles. Also if you need a bright iPhone developer overseas contact me – I do contract work. Here’s my LinkedIn profile
Showing a popup window in iOS ( +class for download)
NOTE: I have posted a newer version of this same tutorial, which makes use of modern iOS5 and iOS6 features.If you want a more flexible and modern code have a look at the more recent version, which you can find here:http://www.touch-code-magazine.com/showing-a-popup-window-in-ios6-modernized-tutorial-code-download/

Few days ago I remembered Ray mentioned almost a year ago he liked the content popup window in the just released (back then) Pimpy application, and he thought it’d be a cool tutorial for Touch Code Magazine if I wrote how to implement it. I forgot for a long time about this conversation, but it came again to me last week.

So in this article I’m going to talk about:

  • why use a Popup Window (Modal window) over using another UIViewController
  • the mechanics I use to make a visually appealing popup window
  • give you a download of a standalone Objective-C class you can use in your projects

Why use a Popup Window?

Going from one UIViewController to another, for example when you use a navigation controller pattern in your app, tells the user he’s going to another screen, to another task – he expects to do and see something new. If you use a popup window on the other hand to present some content it looks to the user only like a little detour, but still remaining in the general area where he or she is at that moment.

I often use a popup window to show:

  • terms & conditions, manuals or any “additional information” to the screen the user is currently seeing
  • show content from Internet

Another benefit is that your controller’s view won’t be released if you hit a Memory Warning; opposed to presenting another controller, when if you have a warning the not-visible controllers’ views will be released (that’s in case there’s something you need to hold on in your views)

The mechanics behind showing the popup window

Since there’s no UIKit class for showing a popup window, you can just have a UIView which looks like a little window. To make this window modal you’d also want to have a transparent or semi- transparent view covering the screen behind your popup window (so the user can’t interact with the UI behind the popup).

Now- just showing a view on the screen isn’t that nice is it? So a transition is really nice to have: I use flip from right/left to show my popups and it looks really nice.

Let’s have a look at part of my code (also included in the download project below):

Look at how I first add a bgView, which takes up the whole screen (actually the bounds of the superview sview); then I add a view called fauxView – this one has absolutely no visual role, it just has to be in the view hierarchy, so the actual content (inside bigPanelView) can flip around from this empty view. This way it looks like the popup window flips around out of the nothing – very cool trick :) I actually haven’t seen this in another application

There’s one thing to mention about [UIView transitionFromView: toView: …… ]. This method basically flips around one view to another. What you need to know though is that it automatically adds the second view (the “to view”) to the view hierarchy, and at the same time removes the first view (the “from view”) from the view hierarchy.

So, in the code above both first and second views are autoreleased – thus when the transitions finishes the fauxView is removed and deallocated automatically, and the bigPanelView is retained by its superview.

So let’s see how things work:

  1. add a background container
  2. add an empty view inside
  3. create the content view (the popup window), but don’t add to the view hierarchy
  4. flip around the empty view into the content view

Let’s see all this in action!

MTPopupWindow – a standalone class to present popup windows

MTPopupWindow is a very simple free to use class, which gives you the ability to show HTML content with just one line of code. It implements the flip in and flip out transitions I just spoke about and here it is how it looks like:

To use the class download the XCode project below and inside you’ll find a folder called MTPopupWindow – copy this folder inside your project

Include the class:

To open a popup window showing an HTML file from your app’s bundle:

(self is a UIViewController instance, since presumably this is called in an IBAction inside a view controller)
To open a popup window showing a web page:

Where to go from here?

If you need to just show some HTML content – just copy over the MTPopupWindow group and use as described above. If you need to do something fancier inside your popup window – you’re welcome to read trough the very short class code and extend it as you wish.

MTPopupWindow demo project download.

I use this technique of presenting modal content for more than a year now – both in Doodle Booth and Pimpy and it really saves me bulk creating controllers, when I don’t really need them. Hope this was useful to you and if so please do leave me a comment and share it with your friends and colleagues.

Marin

NOTE: I have posted a newer version of this same tutorial, which makes use of blocks, attributed strings, and other iOS5 and iOS6 features.If you want a more flexible and modern code have a look at the more recent version, which you can find here: http://www.touch-code-magazine.com/tutorial-building-advanced-rss-reader-with-ios6/

The post was originally published on the following URL: http://www.touch-code-magazine.com/showing-a-popup-window-in-ios-class-for-download/

Display a popover in Swift

On my first project i needed a popup style presentation that would have worked on iPhone and iPad.
In swift we have the UIPopoverPresentationController to deal with that need.

popover

First you need a ViewController that will be the view of your popover. You can built it in the storyboard or in code.
If its in a storyboard you will access it in code with something like that :

If you built it in code your variable setting will look like something like that :

Then you give it the .Popover modalPresentationStyle and define its preferred size :

And now the tricky part begins, you will need to present your controller and for that configure the popoverPresentationController.
For that task you need to conform to theUIPopoverPresentationControllerDelegate protocol, and your ViewController class will look like :
class ViewController: UIViewController,
UIPopoverPresentationControllerDelegate {

then you setup your presentation controller and present it :

In that example the popover is called from a tapGesture Selector, the location variable is the location of the tap recovered from the Gesture recognizer. If you access your popover from a BarButtonItem you should look at the.presentPopoverFromBarButtonItem property.
In order to work on iPad you need to set the sourceView and the sourceRect (here the sourceView is the view from witch the tapGesture occurs, if you come from a Button the sender will be that button.)

In iOS 8 we have the opportunity to present the popover with the same behavior of an iPad (in popup and not in a modal fullscreen view controller). For that purpose you need to add that method of the protocol :

Et voila !

popover2

popup dialog box in Swift iOS

popup box in Swift iOS

Popup dialog box in Swift iOS was easy before, in case you’re wondering how it’s done now,  here is a simple code that works.

Apple is changing the development standards/method from time. For now this works,  but who knows for how long. Just copy the code and put it where it needs to go.

[social_share/]

override func viewDidAppear(animated: Bool) {
var alert = UIAlertController(title: “Alert”, message: “Message”, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: “Click”, style: UIAlertActionStyle.Default, handler: nil))
alert.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
textField.placeholder = “Enter text:”
textField.secureTextEntry = true
})
self.presentViewController(alert, animated: true, completion: nil)
}

 

iOS SDK: Create a Pop Up window

iOS - Pop Up window

iOS – Popup window

In an iOS project I am currently working on, I was requested to create a pop-up window. Trying to figure out how to do it, I came up with a solution that is pretty easy to implement and very straight forward. All you need is a view controller with a transparent background and a subview (your popup window). After creating the popUpViewController, you can just call it from any other view controller.

Lets see the implementation in depth. First we create a new objective-c class with .xib file. In the header file (.h), add the following code (we need the QuarzCore framework in the project to style the window with rounded corners and shadows):

After that edit the .xib file so it looks like the one in the image below.

popup-xib

Interface Builder view of the popup window.

Now connect the popUpView outlet to the view in the .xib file (the view highlighted int he above image). Next, open the .m file of your class and add the following lines in your viewDidLoad method, so the background view of the window looks transparent:

Note that we set the backgroundColor alpha and not the view alpha because the popup window is a subview of the background view and we do not want it to look transparent too. In the viewDidLoad method we also set the shadow of the window and the cornerRadius (in order to get rounded corners).

Finally lets implement the code that does the trick with pop up fade in and fade out:

The showAnimate method scales the view to 1.3 (130%) of its current size, sets the alpha to 0 and then invokes an animation block with a duration of 0.25 sec that scales the view down to its original size and sets the alpha to 1. The removeAnimate method does exactly the opposite and implements also a completion handler for the animation block, so we know when to remove it from its superview.

Finally we need the IBAction for the close button, so the removeAnimate method is called and the implementation for the showInView method that we declared in our .h file in order to show our popup window on top of other views. The code here is self-explanatory:

Connect the IBAction to the “Close” button you’ve added in your .xib and you are done. The output will look like this:

Popup demonstration

Popup demonstration

 

The code is available on Github alongside with a simple view that demonstrates how this popup can be invoked from other view controllers. A new post regarding the Swift implementation of the PopUp is also available here.

iOS SDK: Pop up window in Swift

popup13

One of the most popular posts in my blog is the tutorial for creating a Pop-up window with iOS SDK using Objective-C. Since then many readers reached me out asking for a Swift version of the Pop-up. In general the process is exactly the same in Swift (except the language used to write the code of course…), so I am not going to dive into the process of creating the .xib files again. For this you can always refer to the original post.

The Pop-up controller is a subclass of UIViewController with a couple of methods for the fade in and fade out animations.

In the viewDidLoad method, I styled the PopUp with rounded corners. You can customize that for square corners or other shadow styles. Note that in Swift we have to implement also the required init(coder aDecoder: NSCoder) and as we’ve created the UI in interface builder using .XIBs the override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) method has to be added too. Add these two methods and we are almost done:

Now that the PopUp is implemented, we have to call it from the view where we want to display it.

A fully working example alongside with the Objective-C and Swift code for the PopUp is available on Github. Feel free to use it in your projects and if you find any issue or glitch, do not hesitate to open an issue on Github.

A video version of the tutorial is provided by Webucator.

Reference: http://blog.typpz.com/2015/01/31/ios-sdk-pop-up-window-in-swift/

How to Format and Display Number to Currency in Java – Example Tutorial

Displaying financial amount in respective currency is common requirement in Java based E-commerce applications. For example if you are selling products on-line globally, you will show price of product in their local currency rather than USD or some other currency. Storing price in every currency is not a good option because of maintenance, and more realistically fluctuation in exchange rates. That’s why many of these application prefer stores price of books, electronic goods or whatever product they are selling in USD, and responsibility of converting that price to local currency and displaying is left to client side code. If your client is Java based client e.g. Swing GUI , Java FX client, or a JSP web page, you can use java.text.NumberFormat class to format currency in Java. NumberFormat class allows you to display price in multiple currencydepending upon Locale supported by your Java version. All you need to do is to get correct Currency Instance based upon Locale, for example to display amount in US dollar, call NumberFormat.getCurrencyInstance() method with Locale as Locale.US, similarly to display currency as UK pound sterling, pass Locale.UK and for displaying money in Japanese Yen, pass Locale.JAPAN. Once you get the correct instance of currencyformatter, all you need to do is call their format() method by passing your number. We will see an example of converting one currency to other and displaying amount in multiple currency in Java. It’s actually very much similar to formatting date in Java, so if you are familiar to that part, it would be easy to format currency as well.

How to format  Currency in Java

how to format Currency in Java NumbeFormat Example

Here is our sample program to format a number e.g. double or int to Currency in Java e.g. USD or GBP with dollar and pound sign. When we say formatting currency or formatting number as currency, what we mean is to show respective currency sign in front of price/amount. For example to display prince in USD, you will show something like $100.25. Similarly to show same price to UK customer in Pound, you will show £60.15 and if you are doing business in Asia, and have  Japanese customers, you will show prince in their local currency as well e.g. Japanese Yen, like ¥10,279. Did you notice currency sign in front of amount or price? This is what makes that number and amount rather than just a number. Java also put comma, just like the way write a bigger amount. This type of Locale specific issue becomes really tricky if you are doing this by hard-coding. Suppose you started your business with USD, and then start selling your product on England, if you keep storing price in database in multiple currency you will face hell lot of issue. Instead of that, just keep price in one currency in database, get the latest FX rate and covert price in real time for display. Java API has rich support for formatting currency by java.text.NumberFormat class, and good knowledge of it will save lot of your time, so do invest some time on this class. Also this program is compiled using JDK 1.7, did you notice use of String in Switch case, though I am not a big fan of it in production code, it works perfectly in short tutorials.

import java.text.NumberFormat;
import java.util.Locale;

/**
 * How to format Number to different currency in Java. Following Java program
 * will show you, how you can display double value in different currency e.g.
 * USD, GBP and JPY. This example show price in multiple currency.
 * 
* @author
 */
public class Test {

    public static void main(String args[]) {
        double price = 100.25;

        showPriceInUSD(price, getExchangeRate("USD"));
        showPriceInGBP(price, getExchangeRate("GBP"));
        showPriceInJPY(price, getExchangeRate("JPY"));

    }

    /**
     * Display price in US Dollar currency
     *
     * @param price
     * @param rate
     */
    public static void showPriceInUSD(double price, double rate) {
        double priceInUSD = price * rate;
        NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.US);
        System.out.printf("Price in USD : %s %n", currencyFormat.format(priceInUSD));

    }

    /**
     * Display prince in British Pound
     *
     * @param price
     * @param rate
     */
    public static void showPriceInGBP(double price, double rate) {
        double princeInGBP = price * rate;
        NumberFormat GBP = NumberFormat.getCurrencyInstance(Locale.UK);
        System.out.printf("Price in GBP : %s %n", GBP.format(princeInGBP));
    }

    /**
     * Display prince in Japanese Yen
     *
     * @param price
     * @param rate
     */
    public static void showPriceInJPY(double price, double rate) {
        double princeInJPY = price * rate;
        NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.JAPAN);
        System.out.printf("Price in JPY : %s %n", currency.format(princeInJPY));
    }

    /**
     * @return FX exchange rate for USD
     * @param currency
     */
    public static double getExchangeRate(String currency) {
        switch (currency) {
            case "USD":
                return 1;
            case "JPY":
                return 102.53;
            case "GBP":
                return 0.60;
            case "EURO":
                return 0.73;
            default:
                throw new IllegalArgumentException(String.format("No rates available for currency %s %n", currency));
        }
    }

}


Output
Price in USD : $100.25 
Price in GBP : £60.15

Price in JPY : ¥10,279

That’s all about how to display currency in Java, or how to format a number like integer or double to currency in Java. Remember, NumberFormat class is on java.text package and not on java.util package. I have instance when people searching for this class, thinking that by importing java.util.* they should get NumberFormat class as well. Let me know if you face any issue while formatting currency or displaying amount in multiple currency to Java clients e.g. Swing, Java FX or JSP pages.

Read more: http://javarevisited.blogspot.com/2014/02/how-to-format-and-display-number-to.html#ixzz3vgIPk13U

By Rz Rasel Posted in Java

Simplest Lazy Loading ListView Example in Android with data populated from a MySQL database using php

many implementations of Lazy Loading ListView.
Today I will show you my method to create Lazy Loading ListViews.

I dont know whether it can be any more simple than this.

In this I am using AsyncTask to download each image.

So we will start.

Here we have 6 classes.

1. MainPage – This is our main activity that loads the ListView.
2. GetDataFromDB – This class get the image urls and it’s description as a string from the DB server and return that string.
Then we will split that string to seperate the urls and description and then set it to listview.
3. MyCustomArrayAdapter – Custom adapter for the ListView.
4. Model _ this class creates the object to be set for the ListView for each row.
5. PbAndImage – This class has an imageView and ProgressBar to send to the Asynctask.
6. DownloadImageTask – This class Asynchronously downloads the image.

At last the PHP Script which simple echoes the string containing the URLS and description of the image.

Here is the PHP Script.

getImageUrlsAndDescription.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
// get these values from your DB.
echo    "http://coderzheaven.com/sample_folder/android_1.png,ANDROID0 ##
        http://coderzheaven.com/sample_folder/coderzheaven.png,ANDROID1 ##
        http://coderzheaven.com/sample_folder/coderzheaven.png,ANDROID2 ##
        http://coderzheaven.com/sample_folder/coderzheaven.png,ANDROID3 ##
        http://coderzheaven.com/sample_folder/android_1.png,ANDROID4 ##
        http://coderzheaven.com/sample_folder/coderzheaven.png,ANDROID5 ##
        http://coderzheaven.com/sample_folder/coderzheaven.png,ANDROID6 ##
        http://coderzheaven.com/sample_folder/android_1.png,ANDROID7 ##
        http://coderzheaven.com/sample_folder/coderzheaven.png,ANDROID8 ##
        http://coderzheaven.com/sample_folder/coderzheaven.png,ANDROID9";
?>

So we will start.

MainPage.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.coderzheaven.lazyloadinglistwithphpconnection;
import java.util.<span id="IL_AD8" class="IL_AD">ArrayList</span>;
import java.util.List;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.os.<span id="IL_AD9" class="IL_AD">Bundle</span>;
import android.widget.ArrayAdapter;
public     class     MainPage    extends ListActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.   layout.    contacts_list);
        final List<Model> list = new ArrayList<Model>();
        
        /** This block is for getting the image url to download from the server **/
        final GetDataFromDB getvalues = new GetDataFromDB();
        
        final ProgressDialog dialog = ProgressDialog.show(MainPage.this,
                "", "Gettting values from DB", true);
        new    Thread   (new Runnable() {
            public void run() {
                String response = getvalues.getImageURLAndDesciptionFromDB();
                System.out.println("Response : " + response);
                
                dismissDialog(dialog);
                if (!response.equalsIgnoreCase("")) {
                    if (!response.equalsIgnoreCase("error")) {
                        dismissDialog(dialog);
                        
                        // Got the response, now split it to get the image Urls and description
                        String all[] = response.split("##");
                        for(int k = 0; k < all.length; k++){
                            String urls_and_desc[] = all[k].split(","); //  urls_and_desc[0] contains image url and [1] -> description.
                            list.add(get(urls_and_desc[1],urls_and_desc[0]));
                        }
                    }
                    
                } else {
                    dismissDialog(dialog);
                }
            }
        }).start();
        /*************************** GOT data from Server ********************************************/
        ArrayAdapter<Model> adapter = new MyCustomArrayAdapter(this, list);
        setListAdapter(adapter);
    }
    public void dismissDialog(final ProgressDialog dialog){
        runOnUiThread(new Runnable() {
            public void run() {
                dialog.dismiss();
            }
        });
    }
    private Model get(String s, String url) {
        return new Model(s, url);
    }
}

GetDataFromDB.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.coderzheaven.lazyloadinglistwithphpconnection;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
public class GetDataFromDB {
    public String getImageURLAndDesciptionFromDB() {
        try {
            HttpPost httppost;
            HttpClient httpclient;
            httpclient = new DefaultHttpClient();
            httppost = new HttpPost(
                    "http://10.0.2.2/test/getImageUrlsAndDescription.php"); // change this to your URL.....
            ResponseHandler<String> responseHandler = new BasicResponseHandler();
            final String response = httpclient.execute(httppost,
                    responseHandler);
            
            return response;
        } catch (Exception e) {
            System.out.println("ERROR : " + e.getMessage());
            return "error";
        }
    }
}

Model.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.coderzheaven.lazyloadinglistwithphpconnection;
public class Model {
     
    private String name;
    private String url;
    
    public Model(String name, String url) {
        this.name = name;
        this.url = url;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getURL() {
        return url;
    }
    public void setURL(String url) {
        this.url = url;
    }
}

MyCustomArrayAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.coderzheaven.lazyloadinglistwithphpconnection;
import java.util.List;
import android.app.Activity;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MyCustomArrayAdapter extends ArrayAdapter<Model> {
    private final Activity context;
    private final List<Model> list;
    public MyCustomArrayAdapter(Activity context, List<Model> list) {
        super(context, R.layout.list_layout, list);
        this.context = context;
        this.list = list;
    }
    static class ViewHolder {
        protected TextView text;
        protected ImageView image;
        protected ProgressBar pb;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = null;
        if (convertView == null) {
            LayoutInflater inflator = context.getLayoutInflater();
            view = inflator.inflate(R.layout.list_layout, null);
            final ViewHolder viewHolder = new ViewHolder();
            viewHolder.text = (TextView) view.findViewById(R.id.label);
            viewHolder.text.setTextColor(Color.BLACK);
            viewHolder.image = (ImageView) view.findViewById(R.id.image);
            viewHolder.image.setVisibility(View.GONE);
            viewHolder.pb = (ProgressBar) view.findViewById(R.id.progressBar1);
            view.setTag(viewHolder);
        } else {
            view = convertView;
        }
        ViewHolder holder = (ViewHolder) view.getTag();
        holder.text.setText(list.get(position).getName());
        holder.image.setTag(list.get(position).getURL());
        holder.image.setId(position);
        PbAndImage pb_and_image = new PbAndImage();
        pb_and_image.setImg(holder.image);
        pb_and_image.setPb(holder.pb);
        new DownloadImageTask().execute(pb_and_image);
        return view;
    }
}

PbAndImage.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.coderzheaven.lazyloadinglistwithphpconnection;
import android.widget.ImageView;
import android.widget.ProgressBar;
public class PbAndImage {
    
    private ImageView img;
    private ProgressBar pb;
    public ImageView getImg() {
        return img;
    }
    public void setImg(ImageView img) {
        this.img = img;
    }
    public ProgressBar getPb() {
        return pb;
    }
    public void setPb(ProgressBar pb) {
        this.pb = pb;
    }
}

DownloadImageTask.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.coderzheaven.lazyloadinglistwithphpconnection;
import java.io.InputStream;
import java.net.URL;
import com.coderzheaven.lazyloadinglistwithphpconnection.PbAndImage;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.AsyncTask;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
public class DownloadImageTask extends AsyncTask<PbAndImage, Void, Bitmap> {
    ImageView imageView = null;
    ProgressBar pb = null;
    protected Bitmap doInBackground(PbAndImage... pb_and_images) {
        this.imageView = (ImageView)pb_and_images[0].getImg();
        this.pb = (ProgressBar)pb_and_images[0].getPb();
        return getBitmapDownloaded((String) imageView.getTag());
    }
    protected void onPostExecute(Bitmap result) {
        System.out.println("<span id="IL_AD10" class="IL_AD">Downloaded</span> " + imageView.getId());
        imageView.setVisibility(View.VISIBLE);
        pb.setVisibility(View.GONE);  // hide the progressbar after <span id="IL_AD6" class="IL_AD">downloading</span> the image.
        imageView.setImageBitmap(result); //set the bitmap to the imageview.
    }
    /** This function downloads the image and returns the Bitmap **/
    private Bitmap getBitmapDownloaded(String url) {
        System.out.println("String URL " + url);
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
                    .getContent());
            bitmap = getResizedBitmap(bitmap, 50, 50);
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
    
    /** decodes image and scales it to reduce memory consumption **/
    public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth) {
        int width = bm.getWidth();
        int height = bm.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);
        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }
}

And the Most important thing – DONT FORGET TO ADD THE INTERNET PERMISSION ON YOUR MANIFEST FILE.

1
<uses-permission android:name="android.permission.INTERNET"/>

These are the layout files.

contacts_list.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:layout_margin="10dp" >
    
  <ListView
            android:id="@id/android:list"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:dividerHeight="1dp"
            android:cacheColorHint="#0000"
            android:clipToPadding="true"
            android:layout_margin="5dp"
            android:soundEffectsEnabled="true"
            android:scrollbars="none"
            android:layout_weight="1">
    </ListView>
</LinearLayout>

list_layout.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    
    <ProgressBar
        android:id="@+id/progressBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
     <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:id="@+id/image"
        android:contentDescription="@drawable/ic_launcher">       
    </ImageView>
    
    <TextView
        android:text="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/label"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="5dp"
        android:textStyle="bold"
        android:textSize="20sp">       
    </TextView>
   
</LinearLayout>

Lazy Loading Image

Lazy Loading Image

Lazy Loading Image

Lazy Loading Image

Lazy Loading Image

Please leave your comments on this post.

Join the Forum discussion on this post

NSOperation and NSOperationQueue Tutorial in Swift

Everyone has had the frustrating experience of tapping a button or entering some text in an iOS or Mac app, when all of a sudden – WHAM, the user interface stops being responsive.

On the Mac, your users get to stare at the hourglass or the colorful wheel rotating for a while until they can interact with the UI again. In an iOS app, users expect apps to respond immediately to their touches. Unresponsive apps feel clunky and slow, and usually receive bad reviews.

Keeping your app responsive is easier said than done. Once your app needs to perform more than a handful of tasks, things get complicated quickly. There isn’t much time to perform heavy work in the main run loop and still provide a responsive UI.

What’s a poor developer to do? The solution is to move work off the main thread via concurrency. Concurrency means that your application executes multiple streams (or threads) of operations all at the same time – this way the user interface can stay responsive as you’re performing your work.

One way to perform operations concurrently in iOS is with the NSOperation and NSOperationQueue classes. In this tutorial, you’ll learn how to use them! You’ll start with an app that doesn’t use concurrency at all, so it will appear very sluggish and unresponsive. Then you will rework the application to add concurrent operations and — hopefully — provide a more responsive interface to the user!

Getting Started

The overall goal of the sample project for this tutorial is to show a table view of filtered images. The image will be downloaded from the Internet, have a filter applied, and then be displayed in the table view.

Here’s a schematic view of the app model:

Preliminary Model

Preliminary Model

A First Try

Download the first version of the project that you’ll be working on in this tutorial.

Note: All images are from stock.xchng. Some images in the data source are intentionally mis-named, so that there are instances where an image fails to download to exercise the failure case.

Build and run the project, and (eventually) you’ll see the app running with a list of photos. Try scrolling the list. Painful, isn’t it?

Classic photos, running slowly

Classic photos, running slowly

All of the action is taking place in ListViewController.swift, and most of that is insidetableView(_:cellForRowAtIndexPath:).

Have a look at that method and note there are two things taking place that are quite intensive:

  1. Loading the image data from the web. Even if this is easy work, the app still has to wait for the download to be complete before it can continue.
  2. Filtering the image using Core Image. This method applies a sepia filter to the image. If you would like to know more about Core Image filters, check out Beginning Core Image in Swift

In addition, you’re also loading the list of photos from the web when it is first requested:

  lazy var photos = NSDictionary(contentsOfURL:dataSourceURL)

All of this work is taking place on the main thread of the application. Since the main thread is also responsible for user interaction, keeping it busy with loading things from the web and filtering images is killing the responsiveness of the app. You can get a quick overview of this by using Xcode’s gauges view. You can get to the gauges view by showing the Debug navigator (Command-6) and then selecting CPU while the app is running.

Xcode's Gauges view, showing heavy lifting on the main thread

Xcode’s Gauges view, showing heavy lifting on the main thread

You can see all those spikes in Thread 1, which is the main thread of the app. For more detailed information you can run the app in Instruments, but that’s a whole other tutorial :].

It’s time to think about how can you improve that user experience!

Tasks, Threads and Processes

Before you dive into the tutorial, there are a few technical concepts that need to be dealt with. I’m going to define a few terms:

  • Task: a simple, single piece of work that needs to be done.
  • Thread: a mechanism provided by the operating system that allows multiple sets of instructions to operate at the same time within a single application.
  • Process: an executable chunk of code, which can be made up of multiple threads.

Note: In iOS and OS X, the threading functionality is provided by the POSIX Threads API (or pthreads), and is part of the operating system. This is pretty low level stuff, and you will find that it is easy to make mistakes; perhaps the worst thing about threads is those mistakes can be incredibly hard to find!

The Foundation framework contains a class called NSThread, which is much easier to deal with, but managing multiple threads with NSThread is still a headache. NSOperation and NSOperationQueue are higher level classes that have greatly simplified the process of dealing with multiple threads.

In this diagram, you can see the relationship between a process, threads, and tasks:

Process, Thread and Task

Process, Thread and Task

As you can see, a process can contain multiple threads of execution, and each thread can perform multiple tasks one at a time.

In this diagram, thread 2 performs the work of reading a file, while thread 1 performs user-interface related code. This is quite similar to how you should structure your code in iOS – the main thread should perform any work related to the user interface, and secondary threads should perform slow or long-running operations such as reading files, acccessing the network, etc.

NSOperation vs. Grand Central Dispatch (GCD)

You may have heard of Grand Central Dispatch (GCD). In a nutshell, GCD consists of language features, runtime libraries, and system enhancements to provide systemic and comprehensive improvements to support concurrency on multi-core hardware in iOS and OS X. If you’d like to learn more about GCD, you can read ourMultithreading and Grand Central Dispatch on iOS for Beginners Tutorial.

NSOperation and NSOperationQueue are built on top of GCD. As a very general rule, Apple recommends using the highest-level abstraction, and then dropping down to lower levels when measurements show they are needed.

Here’s a quick comparison of the two that will help you decide when and where to use GCD or NSOperation:

  • GCD is a lightweight way to represent units of work that are going to be executed concurrently. You don’t schedule these units of work; the system takes care of scheduling for you. Adding dependency among blocks can be a headache. Canceling or suspending a block creates extra work for you as a developer! :]
  • NSOperation adds a little extra overhead compared to GCD, but you can add dependency among various operations and re-use, cancel or suspend them.

This tutorial will use NSOperation because you’re dealing with a table view and for performance and power consumption reasons you need to be able to cancel an operation for a specific image if the user has scrolled that image off the screen. Even if the operations are on a background thread, if there are dozens of them waiting on the queue then performance will still suffer.

Redefined App Model

It is time to redefine the preliminary non-threaded model! If you take a closer look at the preliminary model, you see that there are three thread-bogging areas that can be improved. By separating these three areas and placing them in a separate thread, the main thread will be relieved and it can stay responsive to user interactions.

Improved model

Improved model

To get rid of your application bottlenecks, you’ll need a thread specifically to respond to user interactions, a thread dedicated to downloading data source and images, and a thread for performing image filtering. In the new model, the app starts on the main thread and loads an empty table view. At the same time, the app launches a second thread to download the data source.

Once the data source has been downloaded, you’ll tell the table view to reload itself. This has to be done on the main thread, since it involves the user interface. At this point, the table view knows how many rows it has, and it knows the URL of the images it needs to display, but it doesn’t have the actual images yet! If you immediately started to download all images at this point, it would be terribly inefficient, as you don’t need all the images at once!

What can be done to make this better?

A better model is just to start downloading the images whose respective rows are visible on the screen. So your code will first ask the table view which rows are visible, and only then will it start the download process. As well, the image filtering process can’t be started before the image is completely downloaded. Therefore, the code should not start the image filtering process until there is an unfiltered image waiting to be processed.

To make the app appear more responsive, the code will display the image right away once it is downloaded. It will then kick off the image filtering, and then update the UI to display the filtered image. The diagram below shows the schematic control flow for this process:

NSOperation Control Flow

Control Flow

To achieve these objectives, you will need to track whether the image is currently being downloaded, is finished being downloaded, or if the image filtering has been applied. You will also need to track the status of each operation, and whether it is a downloading or filtering operation, so that you can cancel, pause or resume each as the user scrolls.

Okay! Now you’re ready to get coding! :]

Open the project where you left it off, and add a new Swift File to your project named PhotoOperations.swift. Add the following code:

import UIKit
 
// This enum contains all the possible states a photo record can be in
enum PhotoRecordState {
  case New, Downloaded, Filtered, Failed
}
 
class PhotoRecord {
  let name:String
  let url:NSURL
  var state = PhotoRecordState.New
  var image = UIImage(named: "Placeholder")
 
  init(name:String, url:NSURL) {
    self.name = name
    self.url = url
  }
}
Note: Make sure to import UIKit in the top of the file. By default, Xcode will only import Foundation to Swift files.

This simple class will represent each photo displayed in the app, together with its current state, which defaults to.New for newly created records. The image defaults to a placeholder.

To track the status of each operation, you’ll need a separate class. Add the following definition to the end ofPhotoOperations.swift:

class PendingOperations {
  lazy var downloadsInProgress = [NSIndexPath:NSOperation]()
  lazy var downloadQueue:NSOperationQueue = {
    var queue = NSOperationQueue()
    queue.name = "Download queue"
    queue.maxConcurrentOperationCount = 1
    return queue
    }()
 
  lazy var filtrationsInProgress = [NSIndexPath:NSOperation]()
  lazy var filtrationQueue:NSOperationQueue = {
    var queue = NSOperationQueue()
    queue.name = "Image Filtration queue"
    queue.maxConcurrentOperationCount = 1
    return queue
    }()
}

This class contains two dictionaries to keep track of active and pending download and filter operations for each row in the table, and two operation queues for each type of operation.

All of the values are created lazily, meaning they aren’t initialized until they are first accessed. This improves the performance of your app.

Creating an NSOperationQueue is very straightforward, as you can see. Naming your queues helps, since the names show up in Instruments or the debugger. The maxConcurrentOperationCount is set to 1 here for the sake of this tutorial, to allow you to see operations finishing one by one. You could leave this part out to allow the queue to decide how many operations it can handle at once – this would further improve performance.

How does the queue decide how many operations it can run at once? That’s a good question! :] It depends on the hardware. By default, NSOperationQueue will do some calculation behind the scenes, decide what is best for the particular platform the code is running on, and will launch the maximum possible number of threads.

Consider the following example. Assume the system is idle, and there are lots of resources available, so the queue could launch something like eight simultaneous threads. Next time you run the program, the system could be busy with other unrelated operations which are consuming resources, and the queue will only launch two simultaneous threads. Because you’ve set a maximum concurrent operations count, in this app only one operation will happen at a time.

Note:You might wonder why you have to keep track of all active and pending operations. The queue has anoperations method which returns an array of operations, so why not use that? In this project it won’t be very efficient to do so. You need to track which operations are associated with which table view rows, which would involve iterating over the array each time you needed one. Storing them in a dictionary with the index path as a key means lookup is fast and efficient.

It’s time to take care of download and filtration operations. Add the following code to the end ofPhotoOperations.swift:

class ImageDownloader: NSOperation {
  //1
  let photoRecord: PhotoRecord
 
  //2
  init(photoRecord: PhotoRecord) {
    self.photoRecord = photoRecord
  }
 
  //3
  override func main() {
    //4
    if self.cancelled {
      return
    }
    //5
    let imageData = NSData(contentsOfURL:self.photoRecord.url)
 
    //6
    if self.cancelled {
      return
    }
 
    //7
    if imageData?.length > 0 {
      self.photoRecord.image = UIImage(data:imageData!)
      self.photoRecord.state = .Downloaded
    }
    else
    {
      self.photoRecord.state = .Failed
      self.photoRecord.image = UIImage(named: "Failed")
    }
  }
}

NSOperation is an abstract class, designed for subclassing. Each subclass represents a specific task as represented in the diagram earlier.

Here’s what’s happening at each of the numbered comments in the code above:

  1. Add a constant reference to the PhotoRecord object related to the operation.
  2. Create a designated initializer allowing the photo record to be passed in.
  3. main is the method you override in NSOperation subclasses to actually perform work.
  4. Check for cancellation before starting. Operations should regularly check if they have been cancelled before attempting long or intensive work.
  5. Download the image data.
  6. Check again for cancellation.
  7. If there is data, create an image object and add it to the record, and move the state along. If there is no data, mark the record as failed and set the appropriate image.

Next, you’ll create another operation to take care of image filtering! Add the following code to the end ofPhotoOperations.swift:

class ImageFiltration: NSOperation {
  let photoRecord: PhotoRecord
 
  init(photoRecord: PhotoRecord) {
    self.photoRecord = photoRecord
  }
 
  override func main () {
    if self.cancelled {
      return
    }
 
    if self.photoRecord.state != .Downloaded {
      return
    }
 
    if let filteredImage = self.applySepiaFilter(self.photoRecord.image!) {
      self.photoRecord.image = filteredImage
      self.photoRecord.state = .Filtered
    }
  }
}

This looks very similar to the downloading operation, except that you’re applying a filter to the image (using an as yet unimplemented method, hence the compiler error) instead of downloading it.

Add the missing image filter method to the ImageFiltration class:

func applySepiaFilter(image:UIImage) -> UIImage? {
  let inputImage = CIImage(data:UIImagePNGRepresentation(image))
 
  if self.cancelled {
    return nil
  }
  let context = CIContext(options:nil)
  let filter = CIFilter(name:"CISepiaTone")
  filter.setValue(inputImage, forKey: kCIInputImageKey)
  filter.setValue(0.8, forKey: "inputIntensity")
  let outputImage = filter.outputImage
 
  if self.cancelled {
    return nil
  }
 
  let outImage = context.createCGImage(outputImage, fromRect: outputImage.extent())
  let returnImage = UIImage(CGImage: outImage)
  return returnImage
}

The image filtering is the same implementation used previously in ListViewController. It’s been moved here so that it can be done as a separate operation in the background. Again, you should check for cancellation very frequently; a good practice is to do it before and after any expensive method call. Once the filtering is done, you set the values of the photo record instance.

Great! Now you have all the tools and foundation you need in order to process operations as a background tasks. It’s time to go back to the view controller and modify it to take advantage of all these new benefits.

Switch to ListViewController.swift and delete the lazy var photos property declaration. Add the following declarations instead:

var photos = [PhotoRecord]()
let pendingOperations = PendingOperations()

These will hold an array of the PhotoDetails objects you created earlier, and the PendingOperations object to manage the operations.

Add a new method to the class to download the photos property list:

func fetchPhotoDetails() {
  let request = NSURLRequest(URL:dataSourceURL!)
  UIApplication.sharedApplication().networkActivityIndicatorVisible = true
 
  NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {response,data,error in
    if data != nil {
      let datasourceDictionary = NSPropertyListSerialization.propertyListWithData(data, options: Int(NSPropertyListMutabilityOptions.Immutable.rawValue), format: nil, error: nil) as! NSDictionary
 
      for(key : AnyObject,value : AnyObject) in datasourceDictionary {
        let name = key as? String
        let url = NSURL(string:value as? String ?? "")
        if name != nil && url != nil {
          let photoRecord = PhotoRecord(name:name!, url:url!)
          self.photos.append(photoRecord)
        }
      }
 
      self.tableView.reloadData()
    }
 
    if error != nil {
      let alert = UIAlertView(title:"Oops!",message:error.localizedDescription, delegate:nil, cancelButtonTitle:"OK")
      alert.show()
    }
    UIApplication.sharedApplication().networkActivityIndicatorVisible = false
  }
}

This method creates an asynchronous web request which, when finished, will run the completion block on the main queue. When the download is complete the property list data is extracted into an NSDictionary and then processed again into an array of PhotoRecord objects. You haven’t used an NSOperation directly here, but instead you’ve accessed the main queue using NSOperationQueue.mainQueue().

Call the new method at the end of viewDidLoad:

fetchPhotoDetails()

Next, find tableView(_:cellForRowAtIndexPath:) and replace it with the following implementation:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! UITableViewCell
 
  //1
  if cell.accessoryView == nil {
    let indicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
    cell.accessoryView = indicator
  }
  let indicator = cell.accessoryView as! UIActivityIndicatorView
 
  //2
  let photoDetails = photos[indexPath.row]
 
  //3
  cell.textLabel?.text = photoDetails.name
  cell.imageView?.image = photoDetails.image
 
  //4
  switch (photoDetails.state){
  case .Filtered:
    indicator.stopAnimating()
  case .Failed:
    indicator.stopAnimating()
    cell.textLabel?.text = "Failed to load"
  case .New, .Downloaded:
    indicator.startAnimating()
    self.startOperationsForPhotoRecord(photoDetails,indexPath:indexPath)
  }
 
  return cell
}

Take some time to read through the explanation of the commented sections below:

  1. To provide feedback to the user, create a UIActivityIndicatorView and set it as the cell’s accessory view.
  2. The data source contains instances of PhotoRecord. Fetch the right one based on the current row’s indexPath.
  3. The cell’s text label is (nearly) always the same and the image is set appropriately on the PhotoRecord as it is processed, so you can set them both here, regardless of the state of the record.
  4. Inspect the record. Set up the activity indicator and text as appropriate, and kick off the operations (not yet implemented)

You can remove the implementation of applySepiaFilter since that won’t be called any more. Add the following method to the class to start the operations:

func startOperationsForPhotoRecord(photoDetails: PhotoRecord, indexPath: NSIndexPath){
  switch (photoDetails.state) {
  case .New:
    startDownloadForRecord(photoDetails, indexPath: indexPath)
  case .Downloaded:
    startFiltrationForRecord(photoDetails, indexPath: indexPath)
  default:
    NSLog("do nothing")
  }
}

Here, you’ll pass in an instance of PhotoRecord along with its index path. Depending on the photo record’s state, you kick off either the download or filter steps.

Note: the methods for downloading and filtering images are implemented separately, as there is a possibility that while an image is being downloaded the user could scroll away, and you won’t yet have applied the image filter. So next time the user comes to the same row, you don’t need to re-download the image; you only need to apply the image filter! Efficiency rocks! :]

Now you need to implement the methods that you called in the method above. Remember that you created a custom class, PendingOperations, to keep track of operations; now you actually get to use it! Add the following methods to the class:

func startDownloadForRecord(photoDetails: PhotoRecord, indexPath: NSIndexPath){
  //1
  if let downloadOperation = pendingOperations.downloadsInProgress[indexPath] {
    return
  }
 
  //2
  let downloader = ImageDownloader(photoRecord: photoDetails)
  //3
  downloader.completionBlock = {
    if downloader.cancelled {
      return
    }
    dispatch_async(dispatch_get_main_queue(), {
      self.pendingOperations.downloadsInProgress.removeValueForKey(indexPath)
      self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    })
  }
  //4
  pendingOperations.downloadsInProgress[indexPath] = downloader
  //5
  pendingOperations.downloadQueue.addOperation(downloader)
}
 
func startFiltrationForRecord(photoDetails: PhotoRecord, indexPath: NSIndexPath){
  if let filterOperation = pendingOperations.filtrationsInProgress[indexPath]{
    return
  }
 
  let filterer = ImageFiltration(photoRecord: photoDetails)
  filterer.completionBlock = {
    if filterer.cancelled {
      return
    }
    dispatch_async(dispatch_get_main_queue(), {
      self.pendingOperations.filtrationsInProgress.removeValueForKey(indexPath)
      self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
      })
  }
  pendingOperations.filtrationsInProgress[indexPath] = filterer
  pendingOperations.filtrationQueue.addOperation(filterer)
}

Okay! Here’s a quick list to make sure you understand what’s going on in the code above:

  1. First, check for the particular indexPath to see if there is already an operation in downloadsInProgress for it. If so, ignore it.
  2. If not, create an instance of ImageDownloader by using the designated initializer.
  3. Add a completion block which will be executed when the operation is completed. This is a great place to let the rest of your app know that an operation has finished. It’s important to note that the completion block is executed even if the operation is cancelled, so you must check this property before doing anything. You also have no guarantee of which thread the completion block is called on, so you need to use GCD to trigger a reload of the table view on the main thread.
  4. Add the operation to downloadsInProgress to help keep track of things.
  5. Add the operation to the download queue. This is how you actually get these operations to start running – the queue takes care of the scheduling for you once you’ve added the operation.

The method to filter the image follows the same pattern, except it uses ImageFiltration andfiltrationsInProgress to track the operations. As an exercise, you could try getting rid of the repetition in this section of code :]

You made it! Your project is complete. Build and run to see your improvements in action! As you scroll through the table view, the app doesn’t stall anymore, and starts downloading images and filtering them as they become visible.

Classic photos, now with actual scrolling!

Classic photos, now with actual scrolling!

Isn’t that cool? You can see how a little effort can go a long way towards making your applications a lot more responsive — and a lot more fun for the user!

Fine tuning

You’ve come a long way in this tutorial! Your little project is responsive and shows lots of improvement over the original version. However, there are still some small details that are left to take care of. You want to be a great programmer, not just a good one!

You may have noticed that as you scroll away in table view, those offscreen cells are still in the process of being downloaded and filtered. If you scroll quickly, the app will be busy downloading and filtering images from the cells further back in the list even though they aren’t visible. Ideally, the app should cancel filtering of off-screen cells and prioritize the cells that are currently displayed.

Didn’t you put cancellation provisions in your code? Yes, you did — now you should probably make use of them! :]

Go back to Xcode, and open ListViewController.swift. Go to the implementation oftableView(_:cellForRowAtIndexPath:), and wrap the call to startOperationsForPhotoRecord in an if-clause as follows:

if (!tableView.dragging && !tableView.decelerating) {
  self.startOperationsForPhotoRecord(photoDetails, indexPath: indexPath)
}

You tell the table view to start operations only if the table view is not scrolling. These are actually properties ofUIScrollView, and because UITableView is a subclass of UIScrollView, you automatically inherit these properties.

Next, add the implementation of the following UIScrollView delegate methods to the class:

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
  //1
  suspendAllOperations()
}
 
override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  // 2
  if !decelerate {
    loadImagesForOnscreenCells()
    resumeAllOperations()
  }
}
 
override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
  // 3
  loadImagesForOnscreenCells()
  resumeAllOperations()
}

A quick walkthrough of the code above shows the following:

  1. As soon as the user starts scrolling, you will want to suspend all operations and take a look at what the user wants to see. You will implement suspendAllOperations in just a moment.
  2. If the value of decelerate is false, that means the user stopped dragging the table view. Therefore you want to resume suspended operations, cancel operations for offscreen cells, and start operations for onscreen cells. You will implement loadImagesForOnscreenCells and resumeAllOperations in a little while as well.
  3. This delegate method tells you that table view stopped scrolling, so you will do the same as in #2.

Now, add the implementation of these missing methods to ListViewController.swift:

func suspendAllOperations () {
  pendingOperations.downloadQueue.suspended = true
  pendingOperations.filtrationQueue.suspended = true
}
 
func resumeAllOperations () {
  pendingOperations.downloadQueue.suspended = false
  pendingOperations.filtrationQueue.suspended = false
}
 
func loadImagesForOnscreenCells () {
  //1
  if let pathsArray = tableView.indexPathsForVisibleRows() {
    //2
    var allPendingOperations = Set(pendingOperations.downloadsInProgress.keys.array)
    allPendingOperations.unionInPlace(pendingOperations.filtrationsInProgress.keys.array)
 
    //3
    var toBeCancelled = allPendingOperations
    let visiblePaths = Set(pathsArray as! [NSIndexPath])
    toBeCancelled.subtractInPlace(visiblePaths)
 
    //4
    var toBeStarted = visiblePaths
    toBeStarted.subtractInPlace(allPendingOperations)
 
    // 5
    for indexPath in toBeCancelled {
      if let pendingDownload = pendingOperations.downloadsInProgress[indexPath] {
        pendingDownload.cancel()
      }
      pendingOperations.downloadsInProgress.removeValueForKey(indexPath)
      if let pendingFiltration = pendingOperations.filtrationsInProgress[indexPath] {
        pendingFiltration.cancel()
      }
      pendingOperations.filtrationsInProgress.removeValueForKey(indexPath)
    }
 
    // 6
    for indexPath in toBeStarted {
      let indexPath = indexPath as NSIndexPath
      let recordToProcess = self.photos[indexPath.row]
      startOperationsForPhotoRecord(recordToProcess, indexPath: indexPath)
    }
  }
}

suspendAllOperations and resumeAllOperations have a straightforward implementation. NSOperationQueues can be suspended, by setting the suspended property to true. This will suspend all operations in a queue — you can’t suspend operations individually.

loadImagesForOnscreenCells is a little more complex. Here’s what’s going on:

  1. Start with an array containing index paths of all the currently visible rows in the table view.
  2. Construct a set of all pending operations by combining all the downloads in progress + all the filters in progress.
  3. Construct a set of all index paths with operations to be cancelled. Start with all operations, and then remove the index paths of the visible rows. This will leave the set of operations involving off-screen rows.
  4. Construct a set of index paths that need their operations started. Start with index paths all visible rows, and then remove the ones where operations are already pending.
  5. Loop through those to be cancelled, cancel them, and remove their reference from PendingOperations.
  6. Loop through those to be started, and call startOperationsForPhotoRecord for each.

Build and run and you should have a more responsive, and better resource-managed application! Give yourself a round of applause!

Classic photos, loading things one step at a time!

Classic photos, loading things one step at a time!

Notice that when you finish scrolling the table view, the images on the visible rows will start processing right away.

Where To Go From Here?

Here is the completed version of the project.

If you completed this project and took the time to really understand it, congratulations! You can consider yourself a much more valuable iOS developer than you were at the beginning of this tutorial! Most development shops are lucky to have one or two people that really know this stuff.

But beware — like deeply-nested blocks, gratuitous use of multithreading can make a project incomprehensible to people who have to maintain your code. Threads can introduce subtle bugs that may never appear until your network is slow, or the code is run on a faster (or slower) device, or one with a different number of cores. Test very carefully, and always use Instruments (or your own observations) to verify that introducing threads really has made an improvement.

A useful feature of operations that isn’t covered here is dependency. You can make an operation dependent on one or more other operations. This operation then won’t start until the operations it depends on are all finished. For example:

// MyDownloadOperation is a subclass of NSOperation
let downloadOperation = MyDownloadOperation()
// MyFilterOperation  is a subclass of NSOperation
let filterOperation = MyFilterOperation()
 
filterOperation.addDependency(downloadOperation)

To remove dependencies:

filterOperation.removeDependency(downloadOperation)

Could the code in this project be simplified or improved by using dependencies? Put your new skills to use and try it :] An important thing to note is that a dependent operation will still be started if the operations it depends on are cancelled, as well as if they finish naturally. You’ll need to bear that in mind.

If you have any comments or questions about this tutorial or NSOperations in general, please join the forum discussion below!