RSS

(root)/iphone/common : /source/BonjourSupport/BrowserViewController.m (revision 100)

To get this branch, use:
bzr branch /browse/iphone/common
Line Revision Contents
1 46
/*
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
		}
113
114
		// Make sure we have a chance to discover devices before showing the user that nothing was found (yet)
115
		[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(initialWaitOver:) userInfo:nil repeats:NO];
116
	}
117
118
	return self;
119
}
120
121
- (NSString *)searchingForServicesString {
122
	return _searchingForServicesString;
123
}
124
125
// Holds the string that's displayed in the table view during service discovery.
126
- (void)setSearchingForServicesString:(NSString *)searchingForServicesString {
127
	if (_searchingForServicesString != searchingForServicesString) {
128 94
129 46
		_searchingForServicesString = [searchingForServicesString copy];
130
131
        // If there are no services, reload the table to ensure that searchingForServicesString appears.
132
		if ([self.services count] == 0) {
133
			[self.tableView reloadData];
134
		}
135
	}
136
}
137
138
// Creates an NSNetServiceBrowser that searches for services of a particular type in a particular domain.
139
// If a service is currently being resolved, stop resolving it and stop the service browser from
140
// discovering other services.
141
- (BOOL)searchForServicesOfType:(NSString *)type inDomain:(NSString *)domain {
142
	
143
	[self stopCurrentResolve];
144
	[self.netServiceBrowser stop];
145
	[self.services removeAllObjects];
146
147
	NSNetServiceBrowser *aNetServiceBrowser = [[NSNetServiceBrowser alloc] init];
148
	if(!aNetServiceBrowser) {
149
        // The NSNetServiceBrowser couldn't be allocated and initialized.
150
		return NO;
151
	}
152
153
	aNetServiceBrowser.delegate = self;
154
	self.netServiceBrowser = aNetServiceBrowser;
155 94
156 46
	[self.netServiceBrowser searchForServicesOfType:type inDomain:domain];
157
158
	[self.tableView reloadData];
159
	return YES;
160
}
161
162
163
- (NSTimer *)timer {
164
	return _timer;
165
}
166
167
// When this is called, invalidate the existing timer before releasing it.
168
- (void)setTimer:(NSTimer *)newTimer {
169
	[_timer invalidate];
170
	_timer = newTimer;
171
}
172
173
174
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
175
	return 1;
176
}
177
178
179
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
180
	// If there are no services and searchingForServicesString is set, show one row to tell the user.
181
	NSUInteger count = [self.services count];
182
	if (count == 0 && self.searchingForServicesString && self.initialWaitOver)
183
		return 1;
184
185
	return count;
186
}
187
188
189
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
190
	static NSString *tableCellIdentifier = @"UITableViewCell";
191
	UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:tableCellIdentifier];
192
	if (cell == nil) {
193 94
		cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tableCellIdentifier];
194 46
	}
195
	
196
	NSUInteger count = [self.services count];
197
	if (count == 0 && self.searchingForServicesString) {
198
        // If there are no services and searchingForServicesString is set, show one row explaining that to the user.
199
        cell.textLabel.text = self.searchingForServicesString;
200
		cell.textLabel.textColor = [UIColor colorWithWhite:0.5 alpha:0.5];
201
		cell.accessoryType = UITableViewCellAccessoryNone;
202
		// Make sure to get rid of the activity indicator that may be showing if we were resolving cell zero but
203
		// then got didRemoveService callbacks for all services (e.g. the network connection went down).
204
		if (cell.accessoryView)
205
			cell.accessoryView = nil;
206
		return cell;
207
	}
208
	
209
	// Set up the text for the cell
210
	NSNetService* service = [self.services objectAtIndex:indexPath.row];
211
	cell.textLabel.text = [service name];
212
	cell.textLabel.textColor = [UIColor blackColor];
213
	cell.accessoryType = self.showDisclosureIndicators ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
214
	
215
	// Note that the underlying array could have changed, and we want to show the activity indicator on the correct cell
216
	if (self.needsActivityIndicator && self.currentResolve == service) {
217
		if (!cell.accessoryView) {
218
			CGRect frame = CGRectMake(0.0, 0.0, kProgressIndicatorSize, kProgressIndicatorSize);
219
			UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithFrame:frame];
220
			[spinner startAnimating];
221
			spinner.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
222
			[spinner sizeToFit];
223
			spinner.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
224
										UIViewAutoresizingFlexibleRightMargin |
225
										UIViewAutoresizingFlexibleTopMargin |
226
										UIViewAutoresizingFlexibleBottomMargin);
227
			cell.accessoryView = spinner;
228
		}
229
	} else if (cell.accessoryView) {
230
		cell.accessoryView = nil;
231
	}
232
	
233
	return cell;
234
}
235
236
237
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
238
	// Ignore the selection if there are no services as the searchingForServicesString cell
239
	// may be visible and tapping it would do nothing
240
	if ([self.services count] == 0)
241
		return nil;
242
	
243
	return indexPath;
244
}
245
246
247
- (void)stopCurrentResolve {
248
	self.needsActivityIndicator = NO;
249
	self.timer = nil;
250
251
	[self.currentResolve stop];
252
	self.currentResolve = nil;
253
}
254
255
256
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
257
	// If another resolve was running, stop it & remove the activity indicator from that cell
258
	if (self.currentResolve) {
259
		// Get the indexPath for the active resolve cell
260
		NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[self.services indexOfObject:self.currentResolve] inSection:0];
261
		
262
		// Stop the current resolve, which will also set self.needsActivityIndicator
263
		[self stopCurrentResolve];
264
		
265
		// If we found the indexPath for the row, reload that cell to remove the activity indicator
266
		if (indexPath.row != NSNotFound)
267
			[self.tableView reloadRowsAtIndexPaths:[NSArray	arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
268
	}
269
	
270
	// Then set the current resolve to the service corresponding to the tapped cell
271
	self.currentResolve = [self.services objectAtIndex:indexPath.row];
272
	[self.currentResolve setDelegate:self];
273
	
274
	// Attempt to resolve the service. A value of 0.0 sets an unlimited time to resolve it. The user can
275
	// choose to cancel the resolve by selecting another service in the table view.
276
	[self.currentResolve resolveWithTimeout:0.0];
277
	
278
	// Make sure we give the user some feedback that the resolve is happening.
279
	// We will be called back asynchronously, so we don't want the user to think we're just stuck.
280
	// We delay showing this activity indicator in case the service is resolved quickly.
281
	self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showWaiting:) userInfo:self.currentResolve repeats:NO];
282
}
283
284
285
// If necessary, sets up state to show an activity indicator to let the user know that a resolve is occuring.
286
- (void)showWaiting:(NSTimer*)timer {
287
	if (timer == self.timer) {
288
		NSNetService* service = (NSNetService*)[self.timer userInfo];
289
		if (self.currentResolve == service) {
290
			self.needsActivityIndicator = YES;
291
292
			NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[self.services indexOfObject:self.currentResolve] inSection:0];
293
			if (indexPath.row != NSNotFound) {
294
				[self.tableView reloadRowsAtIndexPaths:[NSArray	arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
295
				// Deselect the row since the activity indicator shows the user something is happening.
296
				[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
297
			}
298
		}
299
	}
300
}
301
302
303
- (void)initialWaitOver:(NSTimer*)timer {
304
	self.initialWaitOver= YES;
305
	if (![self.services count])
306
		[self.tableView reloadData];
307
}
308
309
310
- (void)sortAndUpdateUI {
311
	// Sort the services by name.
312
	[self.services sortUsingSelector:@selector(localizedCaseInsensitiveCompareByName:)];
313
	[self.tableView reloadData];
314
}
315
316
317
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser didRemoveService:(NSNetService*)service moreComing:(BOOL)moreComing {
318
	// If a service went away, stop resolving it if it's currently being resolved,
319
	// remove it from the list and update the table view if no more events are queued.
320
	if (self.currentResolve && [service isEqual:self.currentResolve]) {
321
		[self stopCurrentResolve];
322
	}
323
	[self.services removeObject:service];
324
	
325
	// 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.
326
	// When moreComing is set, we don't update the UI so that it doesn't 'flash'.
327
	if (!moreComing) {
328
		[self sortAndUpdateUI];
329
	}
330
}	
331
332
333
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser didFindService:(NSNetService*)service moreComing:(BOOL)moreComing {
334
	// If a service came online, add it to the list and update the table view if no more events are queued.
335
	[self.services addObject:service];
336
337
	// 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.
338
	// When moreComing is set, we don't update the UI so that it doesn't 'flash'.
339
	if (!moreComing) {
340
		[self sortAndUpdateUI];
341
	}
342
}	
343
344
345
// This should never be called, since we resolve with a timeout of 0.0, which means indefinite
346
- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict {
347
	[self stopCurrentResolve];
348
	[self.tableView reloadData];
349
}
350
351
352
- (void)netServiceDidResolveAddress:(NSNetService *)service {
353
	assert(service == self.currentResolve);
354
	
355
	[self stopCurrentResolve];
356
	
357
	[self.delegate browserViewController:self didResolveInstance:service];
358
}
359
360
361
- (void)cancelAction {
362
	[self.delegate browserViewController:self didResolveInstance:nil];
363
}
364
365
366
- (void)dealloc {
367
	// Cleanup any running resolve and free memory
368
	[self stopCurrentResolve];
369
	self.services = nil;
370
	[self.netServiceBrowser stop];
371
	self.netServiceBrowser = nil;
372
	
373
}
374
375
376
@end

Loggerhead 1.17 is a web-based interface for Bazaar branches