sábado, 1 de octubre de 2011

Using NSWorkspace with Processes

In a continuation of the Cocoa Snippets experiment, we're going to talk about the basics of NSWorkspace in several parts. First, we're going to look at how you can use this class to work with other applications. You can use NSWorkspace to get a list of running applications. For example:
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
NSArray * apps = [ws launchedApplications];
NSLog (@"%@", apps);
The output would look something like this:
{
  NSApplicationBundleIdentifier = "com.apple.finder"; 
  NSApplicationName = Finder; 
  NSApplicationPath = "/System/Library/CoreServices/Finder.app"; 
  NSApplicationProcessIdentifier = 184; 
  NSApplicationProcessSerialNumberHigh = 0; 
  NSApplicationProcessSerialNumberLow = 1048577; 
}, 
{
  NSApplicationBundleIdentifier = "com.macromates.textmate"; 
  NSApplicationName = TextMate; 
  NSApplicationPath = "/Applications/TextMate.app"; 
  NSApplicationProcessIdentifier = 220; 
  NSApplicationProcessSerialNumberHigh = 0; 
  NSApplicationProcessSerialNumberLow = 2490369; 
},
{
  NSApplicationBundleIdentifier = "com.apple.iTunes"; 
  NSApplicationName = iTunes; 
  NSApplicationPath = "/Applications/iTunes.app"; 
  NSApplicationProcessIdentifier = 242; 
  NSApplicationProcessSerialNumberHigh = 0; 
  NSApplicationProcessSerialNumberLow = 3145729; 
},
The bundle identifier is set in Info.plist, and is the unique string that Launch Services needs to manage the application in the system. The process indentifier is the unix pid number. This could be used, for example, with the 'kill' command in the shell. We can simplify and clean up this output a bit using NSArray's implementation of KVC:
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
BOOL result = [ws launchApplication:@"Safari"];
NSArray * apps;
apps = [ws valueForKeyPath:@"launchedApplications.NSApplicationName"];
NSLog (@"%@", apps);
Which results in this:
(
  Finder,
  TextMate,
  iTunes
)
We can also launch an application. The most simple way to do this is with -launchApplication:
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
BOOL wasLaunched = [ws launchApplication:@"Safari"];
if ( wasLaunched )
  NSLog (@"Safari was launched");
else
  NSLog (@"Safari was not launched");
NSArray * apps;
apps = [ws valueForKeyPath:@"launchedApplications.NSApplicationName"];
NSLog (@"%@", apps);
Keep in mind, though, that Safari may not be actually up and running by the time you reach the next line, even if the result is "true":
MyApp[620] Safari was launched
MyApp[620] (
  Finder, 
  TextMate, 
  iTunes
)
You may want to launch an application but keep your app in the foreground. This can be done with by sending a more detailed launch message:
NSWorkspace * ws = [NSWorkspace sharedWorkspace];

[ws launchAppWithBundleIdentifier: @"com.apple.Safari"
                          options: NSWorkspaceLaunchWithoutActivation
   additionalEventParamDescriptor: NULL
                 launchIdentifier: nil];
Finally, you might want to get an icon for an application. This is done in two steps:
NSWorkspace * ws    = [NSWorkspace sharedWorkspace];
NSString    * path  = [ws fullPathForApplication:@"Safari"];
NSImage     * icon  = [ws iconForFile: path];
NSLog (@"%@", icon);
This will give you something like the following:
NSImage 0x39b940 Size={32, 32} Reps=(
  NSBitmapImageRep 0x39d5e0 Size={128, 128} ColorSpace=NSDeviceRGBColorSpace BPS=8 BPP=32 Pixels=128x128 Alpha=YES Planar=NO Format=0, 
  NSBitmapImageRep 0x39dd20 Size={32, 32} ColorSpace=NSDeviceRGBColorSpace BPS=8 BPP=32 Pixels=32x32 Alpha=YES Planar=NO Format=0, 
  NSBitmapImageRep 0x39d740 Size={16, 16} ColorSpace=NSDeviceRGBColorSpace BPS=8 BPP=32 Pixels=16x16 Alpha=YES Planar=NO Format=0
)
... which is only moderately interesting. What you'd probably want to do to display the icon is to use NSImage's -compositeToPoint:operation: to draw into a view. Alternately, you could just use an NSImageView, and use -setImage:. To open a particular URL in Safari:
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
NSURL * url = [NSURL URLWithString:@"http://theocacao.com/"];
[ws openURL: url];

jueves, 29 de septiembre de 2011

Page Controller with ScrollView

outer.h:
@interface outer : UIViewController {

NSInteger kNumberOfPages;
NSInteger selectedPage;
NSArray* arrRepList;

IBOutlet UIPageControl* pageControl;
IBOutlet UIScrollView* scrollView;

NSMutableArray *viewControllers;
//BOOL pageControlUsed; 
}

@property (nonatomic, retain) UIScrollView *scrollView;
@property (nonatomic, retain) UIPageControl *pageControl;
@property (nonatomic, retain) NSMutableArray *viewControllers;

- (IBAction)changePage:(id)sender;

@end
outer.m
@interface outer (PrivateMethods)

- (void)loadScrollViewWithPage:(int)page;
- (void)scrollViewDidScroll:(UIScrollView *)sender;

@end

@implementation outer

- (void)viewDidLoad {
[super viewDidLoad];
kNumberOfPages = 7;
self.title = @"Outer XIB";

// view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];

// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;

pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = selectedPage;
[pageControl setFrame:CGRectMake(pageControl.frame.origin.x,pageControl.frame.origin.y,pageControl.frame.size.width,pageControl.frame.size.height*(3.0/4.0))];
//CGRect tmp=CGRectMake(320*selectedPage, 0,320,305 );
//[scrollView scrollRectToVisible:tmp animated:YES];
[self changePage:nil];

}

- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= kNumberOfPages) return;

// replace the placeholder if necessary
UIRepDetailsViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[UIRepDetailsSubView alloc] initWithPageNumber:page andRepList:arrRepList];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}

// add the controller's view to the scroll view
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}

- (void)scrollViewDidScroll:(UIScrollView *)sender {
// We don't want a "feedback loop" between the UIPageControl and the scroll delegate in
// which a scroll event generated from the user hitting the page control triggers updates from
// the delegate method. We use a boolean to disable the delegate logic when the page control is used.
/*if (pageControlUsed) {
// do nothing - the scroll was initiated from the page control, not the user dragging
return;
}*/
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;

// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 2];
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
[self loadScrollViewWithPage:page + 2];
// A possible optimization would be to unload the views+controllers which are no longer visible
}

// At the end of scroll animation, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
//pageControlUsed = NO;
}

- (IBAction)changePage:(id)sender {
int page = pageControl.currentPage;

// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 2];
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
[self loadScrollViewWithPage:page + 2];
// update the scroll view to the appropriate page
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:YES];
// Set the boolean used when scrolls originate from the UIPageControl. See scrollViewDidScroll: above.
//pageControlUsed = YES;
}
inner.h
/* all declarations */
int pageNumber;
NSArray* arrRepList;
inner.m
static NSArray *__pageControlColorList = nil;

@implementation inner

// Creates the color list the first time this method is invoked. Returns one color object from the list.
+ (UIColor *)pageControlColorWithIndex:(NSUInteger)index {
if (__pageControlColorList == nil) {
__pageControlColorList = [[NSArray alloc] initWithObjects:[UIColor redColor], [UIColor greenColor], [UIColor magentaColor],
[UIColor blueColor], [UIColor orangeColor], [UIColor brownColor], [UIColor grayColor], nil];
}
// Mod the index by the list length to ensure access remains in bounds.
return [__pageControlColorList objectAtIndex:index % [__pageControlColorList count]];
}

// Load the view nib and initialize the pageNumber ivar.
- (id)initWithPageNumber:(int)page andRepList:(NSArray*) repList{
if (self = [super initWithNibName:@"RepDetailsSubView" bundle:nil]) {
pageNumber = page;
arrRepList = repList;
}
return self;