{"id":199,"date":"2011-04-28T21:46:59","date_gmt":"2011-04-28T19:46:59","guid":{"rendered":"http:\/\/www.smartersoftware.de\/wordpress\/?p=199"},"modified":"2011-04-28T21:48:54","modified_gmt":"2011-04-28T19:48:54","slug":"progressview-in-ios","status":"publish","type":"post","link":"http:\/\/www.smartersoftware.de\/wordpress\/2011\/04\/progressview-in-ios\/","title":{"rendered":"ProgressView in iOS"},"content":{"rendered":"<p>When you are doing some longer running operation in a program, you want to give some feedback to the user. This blog post looks at some ways on how to do this in iOS. I\u2019m using my ongoing work on my next iPhone app \u201cSmarterContacts\u201d [include link] which will find and eliminate duplicates in your iPhone contacts.<\/p>\n<h2>Providing meaningful progress feedback to the user<\/h2>\n<p>One of the strengths of the iPhone interface is its constant and almost immediate feedback to the user. For example, if you scroll through a long TableView, it feels as if you are grabbing the contents and \u201cthrow\u201d it up. You can tell that a lot of work has been put into this user experience, and it\u2019 obvious that you should avoid breaking this feedback loop when writing your own apps.<\/p>\n<p>But sometimes there are operations in an app that are going to take some time. As a rough guideline, anything that takes longer than a few tenths of a second, there should be some feeback to the user that the program is doing something and hasn\u2019t crashed. iOS offers some way of providing this feedback:<\/p>\n<ul>\n<li>ActivityIndicator <a href=\"http:\/\/www.smartersoftware.de\/wordpress\/wp-content\/pics\/863fbeb5a952_7DA0\/image.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px\" title=\"image\" border=\"0\" alt=\"image\" src=\"http:\/\/www.smartersoftware.de\/wordpress\/wp-content\/pics\/863fbeb5a952_7DA0\/image_thumb.png\" width=\"55\" height=\"52\" \/><\/a>      <br \/>In iOS, this is an animated \u201cspinning wheel\u201d that shows that something is still going on. Other OSs use the same idea, but a different metaphor (OSX has the beachball on earlier Mac versions it was watch, Windows used to have an Hourglass and now has a rotating orb.) The ActivityIndicator is the simplest way to provide feedback \u2013 you just add one to the current view, start it spinning, do your operation and then remove the it.      <br \/>One important drawback is that the ActivityIndicator doesn\u2019t give any feedback to the user about actual progress, i.e. how far along is the operation and how much longer is it going to take. This is not a problem for operations that are reliable and take just a few seconds. But for anything longer, the user might give up on the program.<\/li>\n<li>ProgressBar <a href=\"http:\/\/www.smartersoftware.de\/wordpress\/wp-content\/pics\/863fbeb5a952_7DA0\/image_3.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px\" title=\"image\" border=\"0\" alt=\"image\" src=\"http:\/\/www.smartersoftware.de\/wordpress\/wp-content\/pics\/863fbeb5a952_7DA0\/image_thumb_3.png\" width=\"244\" height=\"24\" \/><\/a>      <br \/>This is a bar that starts empty and gets filled when the operation is progressing. When it is completely full, the operation is completed. (A little side note: In some dialogs \u2013 notably in older Windows file copying dialogs and a lot of installation programs \u2013 the bar empties after it has been filled and then starts to fill again, over and over. This degrades the ProgressBar to just an ActivityIndicator with all its disadvantages, but using a lot more space and disappointing the user who will think the operation is done when the bar is filled for the first time. Definitely not recommended.)      <br \/>The ProgressBar provides much more feedback to the user: It shows that the program is still making progress as the bar nudges on and on, and it allows the user to make a rough estimate of how much work is left. However, it requires a bit more work by the programmer to update the ProgressBar. It also requires the programmer to figure out what \u201c80%&quot; done\u201d means \u2013 this is sometimes tricky when there are a lot of different steps involved.<\/li>\n<\/ul>\n<p>In my app one long running operation is comparing the different contacts and figuring out how \u201csimilar\u201d they are to one another. As this requires comparing each record to all other contacts, the program has a characteristic of O(n^2) which quickly gets noticeable when working on more than 100 records. Therefore, I want to provide feedback to the user while this operation runs. As this takes more than a few seconds (even in the simulator, which is a lot faster than my iPhone 3GS), I wanted to use a ProgressBar. I also wanted to provide some additional feedback (such as \u201cat record x of y\u201d and \u201cz seconds remaining\u201d), so I built a nice looking ProgressView.<\/p>\n<h2>A first stab at a ProgressView<\/h2>\n<p>The ProgressView looks like this:<\/p>\n<p><a href=\"http:\/\/www.smartersoftware.de\/wordpress\/wp-content\/pics\/863fbeb5a952_7DA0\/image_4.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px\" title=\"image\" border=\"0\" alt=\"image\" src=\"http:\/\/www.smartersoftware.de\/wordpress\/wp-content\/pics\/863fbeb5a952_7DA0\/image_thumb_4.png\" width=\"206\" height=\"400\" \/><\/a><\/p>\n<p>It consists of a few labels (for the title, the number of records and the remaining time) and a ProgressBar. The trickiest thing here is the positioning of all the items on the screen. (I\u2019m sure the code I have is a bit clumsy and can be improved. Suggestions are welcome, please add a comment.)<\/p>\n<pre class=\"brush: cpp;\">\/\/add top Label\r\ntopLabel = [[UILabel alloc] initWithFrame:\r\n                     CGRectMake(0, gutter, width-4*gutter, 20.)];\r\n[topLabel setCenter: CGPointMake(center.x, center.y - 30.)];\r\n[topLabel setText: @&quot;[Being busy]&quot;];\r\n[topLabel setTextAlignment:UITextAlignmentCenter];\r\n[self addSubview: topLabel];\r\n\r\n\/\/add Progress Bar\r\nprogbar = [[UIProgressView alloc] initWithFrame:\r\n                           CGRectMake(0, 0, width-4*gutter, 20.)];\r\n[progbar setCenter: center];\r\n[progbar setProgress: 0.];\r\n[progbar setProgressViewStyle:UIProgressViewStyleBar];\r\n[self  addSubview: progbar];    \r\n\r\n\/\/add progress Label\r\nprogLabel = [[UILabel alloc] initWithFrame:\r\n                      CGRectMake(0, 0, width-4*gutter , 20.)];\r\n[progLabel setCenter:CGPointMake(center.x, center.y + 10.)];\r\n[progLabel setTextAlignment:UITextAlignmentCenter];\r\n[progLabel setText: [NSString stringWithFormat: @&quot;At record %i of %i&quot;, currentValue, maxValue]];\r\n[self addSubview: progLabel];  \r\n\r\n\/\/ add remaining time label\r\nremainingLabel = [[UILabel alloc] initWithFrame:\r\n                           CGRectMake(0, 0, width-4*gutter , 20.)];\r\n[remainingLabel setCenter:CGPointMake(center.x, center.y + 30.)];\r\n[remainingLabel setTextAlignment:UITextAlignmentCenter];\r\n[remainingLabel setText: @&quot;Calculating remaining time ...&quot;];\r\n[self addSubview: remainingLabel];  <\/pre>\n<p>It is displayed as a little \u201cdialog box\u201d on the screen and \u201cdims\u201d the view that it is called from by using a semi-transparent background:<\/p>\n<pre class=\"brush: cpp;\">[self setBackgroundColor: [UIColor colorWithRed:204.\/255 green:213.\/255 blue:216.\/255 alpha:0.5]];<\/pre>\n<p>The ProgressView can be updated by calling the \u201csetCurrentValue\u201d method which then updates all the labels and the progress bar:<\/p>\n<pre class=\"brush: cpp;\">-(void) setCurrentValue:(NSInteger)newCurrentValue {\r\n    currentValue = newCurrentValue;\r\n    if (currentValue % stepValue == 0) { \/\/% is modulus\r\n        [progLabel setText: [NSString stringWithFormat: @&quot;At record %i of %i&quot;, currentValue, maxValue]];\r\n        [progbar setProgress: (float) (currentValue) \/ maxValue];\r\n        \r\n        if (currentValue &gt; 0 &amp;&amp; abs([startTime timeIntervalSinceNow]) &gt; 0) {\r\n            int remaining = trunc (abs([startTime timeIntervalSinceNow]) \r\n                                   * (maxValue - currentValue) \/ currentValue);\r\n            [remainingLabel setText: [NSString stringWithFormat: @&quot;About %i seconds remaining&quot;, remaining]];\r\n        } else {\r\n            [remainingLabel setText: @&quot;Calculating remaining time ...&quot;];\r\n        }\r\n    }\r\n}<\/pre>\n<p>All that\u2019s left to do, is to open the view at the start of the long-running operation using addSubview: and then update the ProgressView by calling setCurrentValue:. When the operation is done, we just remove the ProgressView using removeFromSuperview.<\/p>\n<p>This almost works .. the ProgressView is displayed at the start and removed at the end of the operation, but then it is not updated during the operation \u2013 which means no feedback to the user and it looks like the program crashed.<\/p>\n<h2>Making it work using threading<\/h2>\n<p>The reason for this behavior is that the screen is not updated while the operation executes \u2013 I\u2019m not exactly sure when the update would occur (unless when the whole operation is over). This is because the simple program is unthreaded, i.e. there is no thread that is available to update the screen. In VisualBasic there is the expression \u201cDo Events\u201d which basically turns control over to the OS to process screen updates, user inputs etc.&#160; There does not seem to be an equivalent expression in iOS, instead we have to delve a bit into threading to separate between the screen updates and the calculation.<\/p>\n<h3>Setting up the threading environment<\/h3>\n<p>In earlier versions of iOS it was quite a lot of work to set up the threading environment. Since version [???] this has been made a lot easier by providing NSOperationQueue and NSInvocationOperation. Now all that is left to is:<\/p>\n<pre class=\"brush: cpp;\">NSOperationQueue *queue = [NSOperationQueue new];\r\nNSInvocationOperation *operation = \r\n  [[NSInvocationOperation alloc] initWithTarget:self\r\n                                       selector:@selector(<long operation running>)\r\n                                         object:nil];\r\n[queue addOperation:operation];\r\n[operation release];<\/pre>\n<p>The NSOperationQueue basically set ups the threading environment, so that as soon as an operation is added to the queue, the execution is started in a secondary thread. <\/p>\n<h3>Updating the ProgressView<\/h3>\n<p>This secondary thread is then executing the long running operation, while the primary thread can still update the screen. Within our operation we can trigger the screen update in the main thread:<\/p>\n<pre class=\"brush: cpp;\">[self performSelectorOnMainThread:@selector(updateProgress) \r\n                       withObject: nil waitUntilDone:YES];<\/pre>\n<p>I\u2019ve had some problems passing an object using the \u201cwithObject:\u201d selector, probably because I was using memory that was only available to the secondary thread. (Probably because it was allocated by the secondary thread.) Instead, I was able to use a property of the view that created the secondary thread. <\/p>\n<h3>Tearing down<\/h3>\n<p>All that was left to do was to remove the view when the operation is done. This is achieved by calling<\/p>\n<pre class=\"brush: cpp;\">[self performSelectorOnMainThread:@selector(calculationDone) withObject:nil waitUntilDone:YES];<\/pre>\n<p>Again, this calls the main thread to notify that the calculation is done and that the ProgressView can be removed.<\/p>\n<h2>Summary<\/h2>\n<p>We\u2019ve managed to build a good looking, informative ProgressView. In addition to implementing the code to display, update and remove the view, we also had to make sure that the ProgressView is updated on the screen by creating and using a secondary thread. The additional code required for the threading was quite small, and it was able to use the original, unchanged code for the ProgressView.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When you are doing some longer running operation in a program, you want to give some feedback to the user. This blog post looks at some ways on how to do this in iOS. I\u2019m using my ongoing work on my next iPhone app \u201cSmarterContacts\u201d [include link] which will find and eliminate duplicates in your [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[32],"tags":[],"class_list":["post-199","post","type-post","status-publish","format-standard","hentry","category-ios"],"_links":{"self":[{"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/posts\/199","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/comments?post=199"}],"version-history":[{"count":1,"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/posts\/199\/revisions"}],"predecessor-version":[{"id":200,"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/posts\/199\/revisions\/200"}],"wp:attachment":[{"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/media?parent=199"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/categories?post=199"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.smartersoftware.de\/wordpress\/wp-json\/wp\/v2\/tags?post=199"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}