RSS

(root)/iphone/tappity : 46 : tappity/source/BonjourSupport/BrowserViewController.m

To get this branch, use:
bzr branch /browse/iphone/tappity

« back to all changes in this revision

Viewing changes to tappity/source/BonjourSupport/BrowserViewController.m

Dömötör Gulyás
2009-10-27 01:03:19
Revision ID: dognotdog@gmail.com-20091027000319-1vrkixkgf9lkud6g
adds tappity project

Show diffs side-by-side

added added

removed removed

 
1
/*
 
2
     File: BrowserViewController.m
 
3
 Abstract:  View controller for the service instance list.
 
4
 This object manages a NSNetServiceBrowser configured to look for Bonjour
 
5
services.
 
6
 It has an array of NSNetService objects that are displayed in a table view.
 
7
 When the service browser reports that it has discovered a service, the
 
8
corresponding NSNetService is added to the array.
 
9
 When a service goes away, the corresponding NSNetService is removed from the
 
10
array.
 
11
 Selecting an item in the table view asynchronously resolves the corresponding
 
12
net service.
 
13
 When that resolution completes, the delegate is called with the corresponding
 
14
NSNetService.
 
15
 
 
16
  Version: 2.8
 
17
 
 
18
 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
 
19
 Inc. ("Apple") in consideration of your agreement to the following
 
20
 terms, and your use, installation, modification or redistribution of
 
21
 this Apple software constitutes acceptance of these terms.  If you do
 
22
 not agree with these terms, please do not use, install, modify or
 
23
 redistribute this Apple software.
 
24
 
 
25
 In consideration of your agreement to abide by the following terms, and
 
26
 subject to these terms, Apple grants you a personal, non-exclusive
 
27
 license, under Apple's copyrights in this original Apple software (the
 
28
 "Apple Software"), to use, reproduce, modify and redistribute the Apple
 
29
 Software, with or without modifications, in source and/or binary forms;
 
30
 provided that if you redistribute the Apple Software in its entirety and
 
31
 without modifications, you must retain this notice and the following
 
32
 text and disclaimers in all such redistributions of the Apple Software.
 
33
 Neither the name, trademarks, service marks or logos of Apple Inc. may
 
34
 be used to endorse or promote products derived from the Apple Software
 
35
 without specific prior written permission from Apple.  Except as
 
36
 expressly stated in this notice, no other rights or licenses, express or
 
37
 implied, are granted by Apple herein, including but not limited to any
 
38
 patent rights that may be infringed by your derivative works or by other
 
39
 works in which the Apple Software may be incorporated.
 
40
 
 
41
 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
 
42
 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
 
43
 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
 
44
 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
 
45
 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
 
46
 
 
47
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
 
48
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 
49
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 
50
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
 
51
 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
 
52
 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
 
53
 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
 
54
 POSSIBILITY OF SUCH DAMAGE.
 
55
 
 
56
 Copyright (C) 2009 Apple Inc. All Rights Reserved.
 
57
 
 
58
 */
 
59
 
 
60
#import "BrowserViewController.h"
 
61
 
 
62
#define kProgressIndicatorSize 20.0
 
63
 
 
64
// A category on NSNetService that's used to sort NSNetService objects by their name.
 
65
@interface NSNetService (BrowserViewControllerAdditions)
 
66
- (NSComparisonResult) localizedCaseInsensitiveCompareByName:(NSNetService*)aService;
 
67
@end
 
68
 
 
69
@implementation NSNetService (BrowserViewControllerAdditions)
 
70
- (NSComparisonResult) localizedCaseInsensitiveCompareByName:(NSNetService*)aService {
 
71
        return [[self name] localizedCaseInsensitiveCompare:[aService name]];
 
72
}
 
73
@end
 
74
 
 
75
 
 
76
@interface BrowserViewController()
 
77
@property (nonatomic, assign, readwrite) BOOL showDisclosureIndicators;
 
78
@property (nonatomic, retain, readwrite) NSMutableArray* services;
 
79
@property (nonatomic, retain, readwrite) NSNetServiceBrowser* netServiceBrowser;
 
80
@property (nonatomic, retain, readwrite) NSNetService* currentResolve;
 
81
@property (nonatomic, retain, readwrite) NSTimer* timer;
 
82
@property (nonatomic, assign, readwrite) BOOL needsActivityIndicator;
 
83
@property (nonatomic, assign, readwrite) BOOL initialWaitOver;
 
84
 
 
85
- (void)stopCurrentResolve;
 
86
- (void)initialWaitOver:(NSTimer*)timer;
 
87
@end
 
88
 
 
89
@implementation BrowserViewController
 
90
 
 
91
@synthesize delegate = _delegate;
 
92
@synthesize showDisclosureIndicators = _showDisclosureIndicators;
 
93
@synthesize currentResolve = _currentResolve;
 
94
@synthesize netServiceBrowser = _netServiceBrowser;
 
95
@synthesize services = _services;
 
96
@synthesize needsActivityIndicator = _needsActivityIndicator;
 
97
@dynamic timer;
 
98
@synthesize initialWaitOver = _initialWaitOver;
 
99
 
 
100
- (id)initWithTitle:(NSString*)title showDisclosureIndicators:(BOOL)show showCancelButton:(BOOL)showCancelButton {
 
101
        
 
102
        if ((self = [super initWithStyle:UITableViewStylePlain])) {
 
103
                self.title = title;
 
104
                _services = [[NSMutableArray alloc] init];
 
105
                self.showDisclosureIndicators = show;
 
106
 
 
107
                if (showCancelButton) {
 
108
                        // add Cancel button as the nav bar's custom right view
 
109
                        UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
 
110
                                                                                  initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAction)];
 
111
                        self.navigationItem.rightBarButtonItem = addButton;
 
112
                        [addButton release];
 
113
                }
 
114
 
 
115
                // Make sure we have a chance to discover devices before showing the user that nothing was found (yet)
 
116
                [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(initialWaitOver:) userInfo:nil repeats:NO];
 
117
        }
 
118
 
 
119
        return self;
 
120
}
 
121
 
 
122
- (NSString *)searchingForServicesString {
 
123
        return _searchingForServicesString;
 
124
}
 
125
 
 
126
// Holds the string that's displayed in the table view during service discovery.
 
127
- (void)setSearchingForServicesString:(NSString *)searchingForServicesString {
 
128
        if (_searchingForServicesString != searchingForServicesString) {
 
129
                [_searchingForServicesString release];
 
130
                _searchingForServicesString = [searchingForServicesString copy];
 
131
 
 
132
        // If there are no services, reload the table to ensure that searchingForServicesString appears.
 
133
                if ([self.services count] == 0) {
 
134
                        [self.tableView reloadData];
 
135
                }
 
136
        }
 
137
}
 
138
 
 
139
// Creates an NSNetServiceBrowser that searches for services of a particular type in a particular domain.
 
140
// If a service is currently being resolved, stop resolving it and stop the service browser from
 
141
// discovering other services.
 
142
- (BOOL)searchForServicesOfType:(NSString *)type inDomain:(NSString *)domain {
 
143
        
 
144
        [self stopCurrentResolve];
 
145
        [self.netServiceBrowser stop];
 
146
        [self.services removeAllObjects];
 
147
 
 
148
        NSNetServiceBrowser *aNetServiceBrowser = [[NSNetServiceBrowser alloc] init];
 
149
        if(!aNetServiceBrowser) {
 
150
        // The NSNetServiceBrowser couldn't be allocated and initialized.
 
151
                return NO;
 
152
        }
 
153
 
 
154
        aNetServiceBrowser.delegate = self;
 
155
        self.netServiceBrowser = aNetServiceBrowser;
 
156
        [aNetServiceBrowser release];
 
157
        [self.netServiceBrowser searchForServicesOfType:type inDomain:domain];
 
158
 
 
159
        [self.tableView reloadData];
 
160
        return YES;
 
161
}
 
162
 
 
163
 
 
164
- (NSTimer *)timer {
 
165
        return _timer;
 
166
}
 
167
 
 
168
// When this is called, invalidate the existing timer before releasing it.
 
169
- (void)setTimer:(NSTimer *)newTimer {
 
170
        [_timer invalidate];
 
171
        [newTimer retain];
 
172
        [_timer release];
 
173
        _timer = newTimer;
 
174
}
 
175
 
 
176
 
 
177
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
 
178
        return 1;
 
179
}
 
180
 
 
181
 
 
182
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 
183
        // If there are no services and searchingForServicesString is set, show one row to tell the user.
 
184
        NSUInteger count = [self.services count];
 
185
        if (count == 0 && self.searchingForServicesString && self.initialWaitOver)
 
186
                return 1;
 
187
 
 
188
        return count;
 
189
}
 
190
 
 
191
 
 
192
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
193
        static NSString *tableCellIdentifier = @"UITableViewCell";
 
194
        UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:tableCellIdentifier];
 
195
        if (cell == nil) {
 
196
                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tableCellIdentifier] autorelease];
 
197
        }
 
198
        
 
199
        NSUInteger count = [self.services count];
 
200
        if (count == 0 && self.searchingForServicesString) {
 
201
        // If there are no services and searchingForServicesString is set, show one row explaining that to the user.
 
202
        cell.textLabel.text = self.searchingForServicesString;
 
203
                cell.textLabel.textColor = [UIColor colorWithWhite:0.5 alpha:0.5];
 
204
                cell.accessoryType = UITableViewCellAccessoryNone;
 
205
                // Make sure to get rid of the activity indicator that may be showing if we were resolving cell zero but
 
206
                // then got didRemoveService callbacks for all services (e.g. the network connection went down).
 
207
                if (cell.accessoryView)
 
208
                        cell.accessoryView = nil;
 
209
                return cell;
 
210
        }
 
211
        
 
212
        // Set up the text for the cell
 
213
        NSNetService* service = [self.services objectAtIndex:indexPath.row];
 
214
        cell.textLabel.text = [service name];
 
215
        cell.textLabel.textColor = [UIColor blackColor];
 
216
        cell.accessoryType = self.showDisclosureIndicators ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
 
217
        
 
218
        // Note that the underlying array could have changed, and we want to show the activity indicator on the correct cell
 
219
        if (self.needsActivityIndicator && self.currentResolve == service) {
 
220
                if (!cell.accessoryView) {
 
221
                        CGRect frame = CGRectMake(0.0, 0.0, kProgressIndicatorSize, kProgressIndicatorSize);
 
222
                        UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithFrame:frame];
 
223
                        [spinner startAnimating];
 
224
                        spinner.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
 
225
                        [spinner sizeToFit];
 
226
                        spinner.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
 
227
                                                                                UIViewAutoresizingFlexibleRightMargin |
 
228
                                                                                UIViewAutoresizingFlexibleTopMargin |
 
229
                                                                                UIViewAutoresizingFlexibleBottomMargin);
 
230
                        cell.accessoryView = spinner;
 
231
                        [spinner release];
 
232
                }
 
233
        } else if (cell.accessoryView) {
 
234
                cell.accessoryView = nil;
 
235
        }
 
236
        
 
237
        return cell;
 
238
}
 
239
 
 
240
 
 
241
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
242
        // Ignore the selection if there are no services as the searchingForServicesString cell
 
243
        // may be visible and tapping it would do nothing
 
244
        if ([self.services count] == 0)
 
245
                return nil;
 
246
        
 
247
        return indexPath;
 
248
}
 
249
 
 
250
 
 
251
- (void)stopCurrentResolve {
 
252
        self.needsActivityIndicator = NO;
 
253
        self.timer = nil;
 
254
 
 
255
        [self.currentResolve stop];
 
256
        self.currentResolve = nil;
 
257
}
 
258
 
 
259
 
 
260
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
261
        // If another resolve was running, stop it & remove the activity indicator from that cell
 
262
        if (self.currentResolve) {
 
263
                // Get the indexPath for the active resolve cell
 
264
                NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[self.services indexOfObject:self.currentResolve] inSection:0];
 
265
                
 
266
                // Stop the current resolve, which will also set self.needsActivityIndicator
 
267
                [self stopCurrentResolve];
 
268
                
 
269
                // If we found the indexPath for the row, reload that cell to remove the activity indicator
 
270
                if (indexPath.row != NSNotFound)
 
271
                        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
 
272
        }
 
273
        
 
274
        // Then set the current resolve to the service corresponding to the tapped cell
 
275
        self.currentResolve = [self.services objectAtIndex:indexPath.row];
 
276
        [self.currentResolve setDelegate:self];
 
277
        
 
278
        // Attempt to resolve the service. A value of 0.0 sets an unlimited time to resolve it. The user can
 
279
        // choose to cancel the resolve by selecting another service in the table view.
 
280
        [self.currentResolve resolveWithTimeout:0.0];
 
281
        
 
282
        // Make sure we give the user some feedback that the resolve is happening.
 
283
        // We will be called back asynchronously, so we don't want the user to think we're just stuck.
 
284
        // We delay showing this activity indicator in case the service is resolved quickly.
 
285
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showWaiting:) userInfo:self.currentResolve repeats:NO];
 
286
}
 
287
 
 
288
 
 
289
// If necessary, sets up state to show an activity indicator to let the user know that a resolve is occuring.
 
290
- (void)showWaiting:(NSTimer*)timer {
 
291
        if (timer == self.timer) {
 
292
                NSNetService* service = (NSNetService*)[self.timer userInfo];
 
293
                if (self.currentResolve == service) {
 
294
                        self.needsActivityIndicator = YES;
 
295
 
 
296
                        NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[self.services indexOfObject:self.currentResolve] inSection:0];
 
297
                        if (indexPath.row != NSNotFound) {
 
298
                                [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
 
299
                                // Deselect the row since the activity indicator shows the user something is happening.
 
300
                                [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
 
301
                        }
 
302
                }
 
303
        }
 
304
}
 
305
 
 
306
 
 
307
- (void)initialWaitOver:(NSTimer*)timer {
 
308
        self.initialWaitOver= YES;
 
309
        if (![self.services count])
 
310
                [self.tableView reloadData];
 
311
}
 
312
 
 
313
 
 
314
- (void)sortAndUpdateUI {
 
315
        // Sort the services by name.
 
316
        [self.services sortUsingSelector:@selector(localizedCaseInsensitiveCompareByName:)];
 
317
        [self.tableView reloadData];
 
318
}
 
319
 
 
320
 
 
321
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser didRemoveService:(NSNetService*)service moreComing:(BOOL)moreComing {
 
322
        // If a service went away, stop resolving it if it's currently being resolved,
 
323
        // remove it from the list and update the table view if no more events are queued.
 
324
        if (self.currentResolve && [service isEqual:self.currentResolve]) {
 
325
                [self stopCurrentResolve];
 
326
        }
 
327
        [self.services removeObject:service];
 
328
        
 
329
        // If moreComing is NO, it means that there are no more messages in the queue from the Bonjour daemon, so we should update the UI.
 
330
        // When moreComing is set, we don't update the UI so that it doesn't 'flash'.
 
331
        if (!moreComing) {
 
332
                [self sortAndUpdateUI];
 
333
        }
 
334
}       
 
335
 
 
336
 
 
337
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser didFindService:(NSNetService*)service moreComing:(BOOL)moreComing {
 
338
        // If a service came online, add it to the list and update the table view if no more events are queued.
 
339
        [self.services addObject:service];
 
340
 
 
341
        // If moreComing is NO, it means that there are no more messages in the queue from the Bonjour daemon, so we should update the UI.
 
342
        // When moreComing is set, we don't update the UI so that it doesn't 'flash'.
 
343
        if (!moreComing) {
 
344
                [self sortAndUpdateUI];
 
345
        }
 
346
}       
 
347
 
 
348
 
 
349
// This should never be called, since we resolve with a timeout of 0.0, which means indefinite
 
350
- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict {
 
351
        [self stopCurrentResolve];
 
352
        [self.tableView reloadData];
 
353
}
 
354
 
 
355
 
 
356
- (void)netServiceDidResolveAddress:(NSNetService *)service {
 
357
        assert(service == self.currentResolve);
 
358
        
 
359
        [service retain];
 
360
        [self stopCurrentResolve];
 
361
        
 
362
        [self.delegate browserViewController:self didResolveInstance:service];
 
363
        [service release];
 
364
}
 
365
 
 
366
 
 
367
- (void)cancelAction {
 
368
        [self.delegate browserViewController:self didResolveInstance:nil];
 
369
}
 
370
 
 
371
 
 
372
- (void)dealloc {
 
373
        // Cleanup any running resolve and free memory
 
374
        [self stopCurrentResolve];
 
375
        self.services = nil;
 
376
        [self.netServiceBrowser stop];
 
377
        self.netServiceBrowser = nil;
 
378
        [_searchingForServicesString release];
 
379
        
 
380
        [super dealloc];
 
381
}
 
382
 
 
383
 
 
384
@end

Loggerhead 1.17 is a web-based interface for Bazaar branches