RSS

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

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

« back to all changes in this revision

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

Dömötör Gulyás
2010-01-18 09:01:40
Revision ID: dognotdog@gmail.com-20100118080140-g8bc7z6dp9ilr8rt
made tappity a standalone tree

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