月度归档:2013年12月

MapView

– (void)mapView:(MKMapView *)sender didSelectAnnotationView:(MKAnnotationView *)aView;

- (MKAnnotationView *)mapView:(MKMapView *)sender
viewForAnnotation:(id )annotation
{
MKAnnotationView *aView = [sender dequeueReusableAnnotationViewWithIdentifier:IDENT];
if (!aView) {
aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation
                                                reuseIdentifier:IDENT];
// set canShowCallout to YES and build aView’s callout accessory views here }
aView.annotation = annotation; // yes, this happens twice if no dequeue
// maybe load up accessory views here (if not too expensive)?
// or reset them and wait until mapView:didSelectAnnotationView: to load actual data
return aView; }
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; request.naturalLanguageQuery = @“Ike’s”;
request.region = ...; // e.g., Stanford campus
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
// response contains an array of MKMapItem which contains MKPlacemark }];
//
//  PhotosByPhotographerMapViewController.m
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "PhotosByPhotographerMapViewController.h"
#import 
#import "Photo+Annotation.h"
#import "ImageViewController.h"

@interface PhotosByPhotographerMapViewController () 
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (nonatomic, strong) NSArray *photosByPhotographer; // of Photo
@property (nonatomic, strong) ImageViewController *imageViewController; // can be nil
@end

@implementation PhotosByPhotographerMapViewController

#pragma mark - Properties

// lazily fetch the photos by our photographer from Core Data

- (NSArray *)photosByPhotographer
{
    if (!_photosByPhotographer) {
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
        request.predicate = [NSPredicate predicateWithFormat:@"whoTook = %@", self.photographer];
        _photosByPhotographer = [self.photographer.managedObjectContext executeFetchRequest:request
                                                                                      error:NULL];
    }
    return _photosByPhotographer;
}

// when our photographer changes, clear out our photosByPhotographer
// and update the map (if self.mapView is even set at this point)

- (void)setPhotographer:(Photographer *)photographer
{
    _photographer = photographer;
    self.title = photographer.name;
    self.photosByPhotographer = nil;
    [self updateMapViewAnnotations];
}

// when the mapView outlet gets set, set its delegate to ourself
// also, update the annotations to be our photosByPhotographer (if set yet)

- (void)setMapView:(MKMapView *)mapView
{
    _mapView = mapView;
    self.mapView.delegate = self;
    [self updateMapViewAnnotations];
}

// remove all existing annotations from the map
// and add all of our photosByPhotographer to the map
// zoom the map to show them all
// if we are capable of showing a photo immediately
//   (i.e. self.imageViewController is not nil)
//   then pick one of the photos at random and show it

- (void)updateMapViewAnnotations
{
    [self.mapView removeAnnotations:self.mapView.annotations];
    [self.mapView addAnnotations:self.photosByPhotographer];
    [self.mapView showAnnotations:self.photosByPhotographer animated:YES];
    if (self.imageViewController) {
        Photo *autoselectedPhoto = [self.photosByPhotographer firstObject];
        if (autoselectedPhoto) {
            [self.mapView selectAnnotation:autoselectedPhoto animated:YES];
            [self prepareViewController:self.imageViewController
                               forSegue:nil
                       toShowAnnotation:autoselectedPhoto];
        }
    }
}

// see if we can find an ImageViewController to show the selected annotation's image
// currently we look at the detail of a split view we are in (if any)

- (ImageViewController *)imageViewController
{
    id detailvc = [self.splitViewController.viewControllers lastObject];
    if ([detailvc isKindOfClass:[UINavigationController class]]) {
        detailvc = [((UINavigationController *)detailvc).viewControllers firstObject];
    }
    return [detailvc isKindOfClass:[ImageViewController class]] ? detailvc : nil;
}

#pragma mark - MKMapViewDelegate

// enhances our callout to have left (UIImageView) and right (UIButton) accessory views
// only does this if we are going to need to segue to a different VC to show a photo
//  (because, if not (i.e. self.imageViewController is not nil), the photo will already be on screen
//   so there is no reason to show its thumbnail or make the user click again on disclosure button)

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation
{
    static NSString *reuseId = @"PhotosByPhotographerMapViewController";
    MKAnnotationView *view = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
    if (!view) {
        view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation
                                               reuseIdentifier:reuseId];
        view.canShowCallout = YES;
        if (!self.imageViewController) {
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 46, 46)];
            view.leftCalloutAccessoryView = imageView;
            UIButton *disclosureButton = [[UIButton alloc] init];
            [disclosureButton setBackgroundImage:[UIImage imageNamed:@"disclosure"] forState:UIControlStateNormal];
            [disclosureButton sizeToFit];
            view.rightCalloutAccessoryView = disclosureButton;
        }
    }
    
    view.annotation = annotation;
    
    return view;
}

// called when the MKAnnotationView (the pin) is clicked on
// either updates the left callout accessory (UIImageView)
// or shows the Photo annotation in self.imageViewController (if available)

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
    if (self.imageViewController) {
        [self prepareViewController:self.imageViewController
                           forSegue:nil
                   toShowAnnotation:view.annotation];
    } else {
        [self updateLeftCalloutAccessoryViewInAnnotationView:view];
    }
}

// checks to be sure that the annotationView's left callout is a UIImageView
// if it is and if the annotation is a Photo, then shows the thumbnail
// this should do that fetch in another thread
// but when the thumbnail image came back, it would need to double check the annotationView
// to be sure it is still displaying the annotation for which we fetched
// (because MKAnnotationViews, like UITableViewCells, are reused)

- (void)updateLeftCalloutAccessoryViewInAnnotationView:(MKAnnotationView *)annotationView
{
    UIImageView *imageView = nil;
    if ([annotationView.leftCalloutAccessoryView isKindOfClass:[UIImageView class]]) {
        imageView = (UIImageView *)annotationView.leftCalloutAccessoryView;
    }
    if (imageView) {
        Photo *photo = nil;
        if ([annotationView.annotation isKindOfClass:[Photo class]]) {
            photo = (Photo *)annotationView.annotation;
        }
        if (photo) {
#warning Blocking main queue!
            imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:photo.thumbnailURL]]];
        }
    }
}

// called when the right callout accessory view is tapped
// (it is the only accessory view we have that inherits from UIControl)
// will crash the program if this View Controller does not have a @"Show Photo" segue
// in the storyboard

- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
    [self performSegueWithIdentifier:@"Show Photo" sender:view];
}

#pragma mark - Navigation

// if the annotation is a Photo, this passes its imageURL to vc (if vc is an ImageViewController)

- (void)prepareViewController:(id)vc
                     forSegue:(NSString *)segueIdentifier
             toShowAnnotation:(id )annotation
{
    Photo *photo = nil;
    if ([annotation isKindOfClass:[Photo class]]) {
        photo = (Photo *)annotation;
    }
    if (photo) {
        if (![segueIdentifier length] || [segueIdentifier isEqualToString:@"Show Photo"]) {
            if ([vc isKindOfClass:[ImageViewController class]]) {
                ImageViewController *ivc = (ImageViewController *)vc;
                ivc.imageURL = [NSURL URLWithString:photo.imageURL];
                ivc.title = photo.title;
            }
        }
    }
}

// if sender is an MKAnnotationView, this calls prepareViewController:forSegue:toShowAnnotation:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([sender isKindOfClass:[MKAnnotationView class]]) {
        [self prepareViewController:segue.destinationViewController
                           forSegue:segue.identifier
                   toShowAnnotation:((MKAnnotationView *)sender).annotation];
    }
}

@end

Photomania Universal URL

ImageViewController.m

//
//  ImageViewController.m
//  Imaginarium
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "ImageViewController.h"
#import "URLViewController.h"

@interface ImageViewController () 
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImage *image;
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *spinner;
// this is weak because we want it to go back to nil
//   when no one else has strong pointer to the popover (i.e. it is dismissed)
@property (weak, nonatomic) UIPopoverController *urlPopoverController;
@end

@implementation ImageViewController

#pragma mark - View Controller Lifecycle

// add the UIImageView to the MVC's View

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.scrollView addSubview:self.imageView];
}

#pragma mark - Properties

// lazy instantiation

- (UIImageView *)imageView
{
    if (!_imageView) _imageView = [[UIImageView alloc] init];
    return _imageView;
}

// image property does not use an _image instance variable
// instead it just reports/sets the image in the imageView property
// thus we don't need @synthesize even though we implement both setter and getter

- (UIImage *)image
{
    return self.imageView.image;
}

- (void)setImage:(UIImage *)image
{
    self.imageView.image = image; // does not change the frame of the UIImageView

    // had to add these two lines in Shutterbug to fix a bug in "reusing" ImageViewController's MVC
    self.scrollView.zoomScale = 1.0;
    self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
    
    // self.scrollView could be nil on the next line if outlet-setting has not happened yet
    self.scrollView.contentSize = self.image ? self.image.size : CGSizeZero;

    // in portrait orientation on an iPad in a split view,
    //   unfortunately the master can be access while popover is up
    //   (so dismiss the URL if someone changes our image from there)
    [self.urlPopoverController dismissPopoverAnimated:YES];

    [self.spinner stopAnimating];
}

- (void)setScrollView:(UIScrollView *)scrollView
{
    _scrollView = scrollView;
    
    // next three lines are necessary for zooming
    _scrollView.minimumZoomScale = 0.2;
    _scrollView.maximumZoomScale = 2.0;
    _scrollView.delegate = self;

    // next line is necessary in case self.image gets set before self.scrollView does
    // for example, prepareForSegue:sender: is called before outlet-setting phase
    self.scrollView.contentSize = self.image ? self.image.size : CGSizeZero;
}

#pragma mark - UIScrollViewDelegate

// mandatory zooming method in UIScrollViewDelegate protocol

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return self.imageView;
}

#pragma mark - Navigation

// show our imageURL in a popover
// stash the popover so that we can ensure that only one appears at a time

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController isKindOfClass:[URLViewController class]]) {
        URLViewController *urlvc = (URLViewController *)segue.destinationViewController;
        // if we are segueing to a popover, the segue itself will be a UIStoryboardPopoverSegue
        if ([segue isKindOfClass:[UIStoryboardPopoverSegue class]]) {
            UIStoryboardPopoverSegue *popoverSegue = (UIStoryboardPopoverSegue *)segue;
            self.urlPopoverController = popoverSegue.popoverController;
        }
        urlvc.url = self.imageURL;
    }
}

// don't show the URL if it's already showing or we don't have a URL to show

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
    if ([identifier isEqualToString:@"Show URL"]) {
        return self.urlPopoverController ? NO : (self.imageURL ? YES : NO);
    } else {
        return [super shouldPerformSegueWithIdentifier:identifier sender:sender];
    }
}

#pragma mark - Setting the Image from the Image's URL

- (void)setImageURL:(NSURL *)imageURL
{
    _imageURL = imageURL;
    //    self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:self.imageURL]]; // blocks main queue!
    [self startDownloadingImage];
}

- (void)startDownloadingImage
{
    self.image = nil;

    if (self.imageURL)
    {
        [self.spinner startAnimating];

        NSURLRequest *request = [NSURLRequest requestWithURL:self.imageURL];
        
        // another configuration option is backgroundSessionConfiguration (multitasking API required though)
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        
        // create the session without specifying a queue to run completion handler on (thus, not main queue)
        // we also don't specify a delegate (since completion handler is all we need)
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];

        NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request
            completionHandler:^(NSURL *localfile, NSURLResponse *response, NSError *error) {
                // this handler is not executing on the main queue, so we can't do UI directly here
                if (!error) {
                    if ([request.URL isEqual:self.imageURL]) {
                        // UIImage is an exception to the "can't do UI here"
                        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:localfile]];
                        // but calling "self.image =" is definitely not an exception to that!
                        // so we must dispatch this back to the main queue
                        dispatch_async(dispatch_get_main_queue(), ^{ self.image = image; });
                    }
                }
        }];
        [task resume]; // don't forget that all NSURLSession tasks start out suspended!
    }
}

#pragma mark - UISplitViewControllerDelegate

// this section added during Shutterbug demo

- (void)awakeFromNib
{
    self.splitViewController.delegate = self;
}

- (BOOL)splitViewController:(UISplitViewController *)svc
   shouldHideViewController:(UIViewController *)vc
              inOrientation:(UIInterfaceOrientation)orientation
{
    return UIInterfaceOrientationIsPortrait(orientation);
}

- (void)splitViewController:(UISplitViewController *)svc
     willHideViewController:(UIViewController *)aViewController
          withBarButtonItem:(UIBarButtonItem *)barButtonItem
       forPopoverController:(UIPopoverController *)pc
{
    barButtonItem.title = aViewController.title;
    self.navigationItem.leftBarButtonItem = barButtonItem;
}

- (void)splitViewController:(UISplitViewController *)svc
     willShowViewController:(UIViewController *)aViewController
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    self.navigationItem.leftBarButtonItem = nil;
}

@end

Photo+Flickr

//
//  Photo+Flickr.m
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "Photo+Flickr.h"
#import "FlickrFetcher.h"
#import "Photographer+Create.h"

@implementation Photo (Flickr)

+ (Photo *)photoWithFlickrInfo:(NSDictionary *)photoDictionary
        inManagedObjectContext:(NSManagedObjectContext *)context
{
    Photo *photo = nil;
    
    NSString *unique = photoDictionary[FLICKR_PHOTO_ID];
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
    request.predicate = [NSPredicate predicateWithFormat:@"unique = %@", unique];
    
    NSError *error;
    NSArray *matches = [context executeFetchRequest:request error:&error];
    
    if (!matches || error || ([matches count] > 1)) {
        // handle error
    } else if ([matches count]) {
        photo = [matches firstObject];
    } else {
        photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo"
                                              inManagedObjectContext:context];
        photo.unique = unique;
        photo.title = [photoDictionary valueForKeyPath:FLICKR_PHOTO_TITLE];
        photo.subtitle = [photoDictionary valueForKeyPath:FLICKR_PHOTO_DESCRIPTION];
        photo.imageURL = [[FlickrFetcher URLforPhoto:photoDictionary format:FlickrPhotoFormatLarge] absoluteString];
        
        NSString *photographerName = [photoDictionary valueForKeyPath:FLICKR_PHOTO_OWNER];
        photo.whoTook = [Photographer photographerWithName:photographerName
                                    inManagedObjectContext:context];

    }

    return photo;
}

+ (void)loadPhotosFromFlickrArray:(NSArray *)photos // of Flickr NSDictionary
         intoManagedObjectContext:(NSManagedObjectContext *)context
{
    for (NSDictionary *photo in photos) {
        [self photoWithFlickrInfo:photo inManagedObjectContext:context];
    }
}

@end

Photographer+Create.m

//
//  Photographer+Create.m
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "Photographer+Create.h"

@implementation Photographer (Create)

+ (Photographer *)photographerWithName:(NSString *)name
                inManagedObjectContext:(NSManagedObjectContext *)context
{
    Photographer *photographer = nil;
    
    if ([name length]) {
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photographer"];
        request.predicate = [NSPredicate predicateWithFormat:@"name = %@", name];
        
        NSError *error;
        NSArray *matches = [context executeFetchRequest:request error:&error];
        
        if (!matches || ([matches count] > 1)) {
            // handle error
        } else if (![matches count]) {
            photographer = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer"
                                                         inManagedObjectContext:context];
            photographer.name = name;
        } else {
            photographer = [matches lastObject];
        }
    }
    
    return photographer;
}

@end

PhotographersCDTVC.m

//
//  PhotographersCDTVC.m
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "PhotographersCDTVC.h"
#import "Photographer.h"
#import "PhotoDatabaseAvailability.h"
#import "PhotosByPhotographerCDTVC.h"

@implementation PhotographersCDTVC

- (void)awakeFromNib
{
    [[NSNotificationCenter defaultCenter] addObserverForName:PhotoDatabaseAvailabilityNotification
                                                      object:nil
                                                       queue:nil
                                                  usingBlock:^(NSNotification *note) {
                                                      self.managedObjectContext = note.userInfo[PhotoDatabaseAvailabilityContext];
                                                  }];
}

- (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
    _managedObjectContext = managedObjectContext;
    
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photographer"];
    request.predicate = nil;
    request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"name"
                                                              ascending:YES
                                                               selector:@selector(localizedStandardCompare:)]];

    
    
    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                        managedObjectContext:managedObjectContext
                                                                          sectionNameKeyPath:nil
                                                                                   cacheName:nil];
}

#pragma mark - UITableViewDataSource

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Photographer Cell"];
    
    Photographer *photographer = [self.fetchedResultsController objectAtIndexPath:indexPath];
    
    cell.textLabel.text = photographer.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%d photos", (int)[photographer.photos count]];
    
    return cell;
}

#pragma mark - Navigation

- (void)prepareViewController:(id)vc forSegue:(NSString *)segueIdentifer fromIndexPath:(NSIndexPath *)indexPath
{
    Photographer *photographer = [self.fetchedResultsController objectAtIndexPath:indexPath];
    // note that we don't check the segue identifier here
    // probably fine ... hard to imagine any other way this class would segue to PhotosByPhotographerCDTVC
    if ([vc isKindOfClass:[PhotosByPhotographerCDTVC class]]) {
        PhotosByPhotographerCDTVC *pbpcdtvc = (PhotosByPhotographerCDTVC *)vc;
        pbpcdtvc.photographer = photographer;
    }
}

// boilerplate
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    NSIndexPath *indexPath = nil;
    if ([sender isKindOfClass:[UITableViewCell class]]) {
        indexPath = [self.tableView indexPathForCell:sender];
    }
    [self prepareViewController:segue.destinationViewController
                       forSegue:segue.identifier
                  fromIndexPath:indexPath];
}

// boilerplate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    id detailvc = [self.splitViewController.viewControllers lastObject];
    if ([detailvc isKindOfClass:[UINavigationController class]]) {
        detailvc = [((UINavigationController *)detailvc).viewControllers firstObject];
        [self prepareViewController:detailvc
                           forSegue:nil
                      fromIndexPath:indexPath];
    }
}

@end

PhotomaniaAppDelegate.m

//
//  PhotomaniaAppDelegate.m
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "PhotomaniaAppDelegate.h"
#import "PhotomaniaAppDelegate+MOC.h"
#import "FlickrFetcher.h"
#import "Photo+Flickr.h"
#import "PhotoDatabaseAvailability.h"

// THIS FILE WANTS TO BE VERY WIDE BECAUSE IT HAS A LOT OF COMMENTS THAT ARE ATTACHED ONTO THE END OF LINES--MAKE THIS COMMENT FIT ON ONE LINE.
// (or turn off line wrapping)

@interface PhotomaniaAppDelegate() 
@property (copy, nonatomic) void (^flickrDownloadBackgroundURLSessionCompletionHandler)();
@property (strong, nonatomic) NSURLSession *flickrDownloadSession;
@property (strong, nonatomic) NSTimer *flickrForegroundFetchTimer;
@property (strong, nonatomic) NSManagedObjectContext *photoDatabaseContext;
@end

// name of the Flickr fetching background download session
#define FLICKR_FETCH @"Flickr Just Uploaded Fetch"

// how often (in seconds) we fetch new photos if we are in the foreground
#define FOREGROUND_FLICKR_FETCH_INTERVAL (20*60)

// how long we'll wait for a Flickr fetch to return when we're in the background
#define BACKGROUND_FLICKR_FETCH_TIMEOUT (10)

@implementation PhotomaniaAppDelegate

#pragma mark - UIApplicationDelegate

// this is called as soon as our storyboard is read in and we're ready to get started
// but it's still very early in the game (UI is not yet on screen, for example)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // when we're in the background, fetch as often as possible (which won't be much)
    // forgot to include this line in the demo during lecture, but don't forget to include it in your app!
    [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];

    // we get our managed object context by creating it ourself in a category on PhotomaniaAppDelegate
    // but in your homework assignment, you must get your context from a UIManagedDocument
    // (i.e. you cannot use the method createMainQueueManagedObjectContext, or even use that approach)
    self.photoDatabaseContext = [self createMainQueueManagedObjectContext];
    
    // we fire off a Flickr fetch every time we launch (why not?)
    [self startFlickrFetch];
    
    // this return value has to do with handling URLs from other applications
    // don't worry about it for now, just return YES
    return YES;
}

// this is called occasionally by the system WHEN WE ARE NOT THE FOREGROUND APPLICATION
// in fact, it will LAUNCH US if necessary to call this method
// the system has lots of smarts about when to do this, but it is entirely opaque to us

- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    // in lecture, we relied on our background flickrDownloadSession to do the fetch by calling [self startFlickrFetch]
    // that was easy to code up, but pretty weak in terms of how much it will actually fetch (maybe almost never)
    // that's because there's no guarantee that we'll be allowed to start that discretionary fetcher when we're in the background
    // so let's simply make a non-discretionary, non-background-session fetch here
    // we don't want it to take too long because the system will start to lose faith in us as a background fetcher and stop calling this as much
    // so we'll limit the fetch to BACKGROUND_FETCH_TIMEOUT seconds (also we won't use valuable cellular data)

    if (self.photoDatabaseContext) {
        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        sessionConfig.allowsCellularAccess = NO;
        sessionConfig.timeoutIntervalForRequest = BACKGROUND_FLICKR_FETCH_TIMEOUT; // want to be a good background citizen!
        NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[FlickrFetcher URLforRecentGeoreferencedPhotos]];
        NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request
            completionHandler:^(NSURL *localFile, NSURLResponse *response, NSError *error) {
                if (error) {
                    NSLog(@"Flickr background fetch failed: %@", error.localizedDescription);
                    completionHandler(UIBackgroundFetchResultNoData);
                } else {
                    [self loadFlickrPhotosFromLocalURL:localFile
                                           intoContext:self.photoDatabaseContext
                                   andThenExecuteBlock:^{
                                       completionHandler(UIBackgroundFetchResultNewData);
                                   }
                     ];
                }
            }];
        [task resume];
    } else {
        completionHandler(UIBackgroundFetchResultNoData); // no app-switcher update if no database!
    }
}

// this is called whenever a URL we have requested with a background session returns and we are in the background
// it is essentially waking us up to handle it
// if we were in the foreground iOS would just call our delegate method and not bother with this

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
    // this completionHandler, when called, will cause our UI to be re-cached in the app switcher
    // but we should not call this handler until we're done handling the URL whose results are now available
    // so we'll stash the completionHandler away in a property until we're ready to call it
    // (see flickrDownloadTasksMightBeComplete for when we actually call it)
    self.flickrDownloadBackgroundURLSessionCompletionHandler = completionHandler;
}

#pragma mark - Database Context

// we do some stuff when our Photo database's context becomes available
// we kick off our foreground NSTimer so that we are fetching every once in a while in the foreground
// we post a notification to let others know the context is available

- (void)setPhotoDatabaseContext:(NSManagedObjectContext *)photoDatabaseContext
{
    _photoDatabaseContext = photoDatabaseContext;
    
    // every time the context changes, we'll restart our timer
    // so kill (invalidate) the current one
    // (we didn't get to this line of code in lecture, sorry!)
    [self.flickrForegroundFetchTimer invalidate];
    self.flickrForegroundFetchTimer = nil;
    
    if (self.photoDatabaseContext)
    {
        // this timer will fire only when we are in the foreground
        self.flickrForegroundFetchTimer = [NSTimer scheduledTimerWithTimeInterval:FOREGROUND_FLICKR_FETCH_INTERVAL
                                         target:self
                                       selector:@selector(startFlickrFetch:)
                                       userInfo:nil
                                        repeats:YES];
    }
    
    // let everyone who might be interested know this context is available
    // this happens very early in the running of our application
    // it would make NO SENSE to listen to this radio station in a View Controller that was segued to, for example
    // (but that's okay because a segued-to View Controller would presumably be "prepared" by being given a context to work in)
    NSDictionary *userInfo = self.photoDatabaseContext ? @{ PhotoDatabaseAvailabilityContext : self.photoDatabaseContext } : nil;
    [[NSNotificationCenter defaultCenter] postNotificationName:PhotoDatabaseAvailabilityNotification
                                                        object:self
                                                      userInfo:userInfo];
}

#pragma mark - Flickr Fetching

// this will probably not work (task = nil) if we're in the background, but that's okay
// (we do our background fetching in performFetchWithCompletionHandler:)
// it will always work when we are the foreground (active) application

- (void)startFlickrFetch
{
    // getTasksWithCompletionHandler: is ASYNCHRONOUS
    // but that's okay because we're not expecting startFlickrFetch to do anything synchronously anyway
    [self.flickrDownloadSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        // let's see if we're already working on a fetch ...
        if (![downloadTasks count]) {
            // ... not working on a fetch, let's start one up
            NSURLSessionDownloadTask *task = [self.flickrDownloadSession downloadTaskWithURL:[FlickrFetcher URLforRecentGeoreferencedPhotos]];
            task.taskDescription = FLICKR_FETCH;
            [task resume];
        } else {
            // ... we are working on a fetch (let's make sure it (they) is (are) running while we're here)
            for (NSURLSessionDownloadTask *task in downloadTasks) [task resume];
        }
    }];
}

- (void)startFlickrFetch:(NSTimer *)timer // NSTimer target/action always takes an NSTimer as an argument
{
    [self startFlickrFetch];
}

// the getter for the flickrDownloadSession @property

- (NSURLSession *)flickrDownloadSession // the NSURLSession we will use to fetch Flickr data in the background
{
    if (!_flickrDownloadSession) {
        static dispatch_once_t onceToken; // dispatch_once ensures that the block will only ever get executed once per application launch
        dispatch_once(&onceToken, ^{
            // notice the configuration here is "backgroundSessionConfiguration:"
            // that means that we will (eventually) get the results even if we are not the foreground application
            // even if our application crashed, it would get relaunched (eventually) to handle this URL's results!
            NSURLSessionConfiguration *urlSessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:FLICKR_FETCH];
            _flickrDownloadSession = [NSURLSession sessionWithConfiguration:urlSessionConfig
                                                                   delegate:self    // we MUST have a delegate for background configurations
                                                              delegateQueue:nil];   // nil means "a random, non-main-queue queue"
        });
    }
    return _flickrDownloadSession;
}

// standard "get photo information from Flickr URL" code

- (NSArray *)flickrPhotosAtURL:(NSURL *)url
{
    NSDictionary *flickrPropertyList;
    NSData *flickrJSONData = [NSData dataWithContentsOfURL:url];  // will block if url is not local!
    if (flickrJSONData) {
        flickrPropertyList = [NSJSONSerialization JSONObjectWithData:flickrJSONData
                                                                           options:0
                                                                             error:NULL];
    }
    return [flickrPropertyList valueForKeyPath:FLICKR_RESULTS_PHOTOS];
}

// gets the Flickr photo dictionaries out of the url and puts them into Core Data
// this was moved here after lecture to give you an example of how to declare a method that takes a block as an argument
// and because we now do this both as part of our background session delegate handler and when background fetch happens

- (void)loadFlickrPhotosFromLocalURL:(NSURL *)localFile
                         intoContext:(NSManagedObjectContext *)context
                 andThenExecuteBlock:(void(^)())whenDone
{
    if (context) {
        NSArray *photos = [self flickrPhotosAtURL:localFile];
        [context performBlock:^{
            [Photo loadPhotosFromFlickrArray:photos intoManagedObjectContext:context];
            [context save:NULL]; // NOT NECESSARY if this is a UIManagedDocument's context
            if (whenDone) whenDone();
        }];
    } else {
        if (whenDone) whenDone();
    }
}

#pragma mark - NSURLSessionDownloadDelegate

// required by the protocol
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)localFile
{
    // we shouldn't assume we're the only downloading going on ...
    if ([downloadTask.taskDescription isEqualToString:FLICKR_FETCH]) {
        // ... but if this is the Flickr fetching, then process the returned data
        [self loadFlickrPhotosFromLocalURL:localFile
                               intoContext:self.photoDatabaseContext
                       andThenExecuteBlock:^{
                           [self flickrDownloadTasksMightBeComplete];
                       }
         ];
    }
}

// required by the protocol
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    // we don't support resuming an interrupted download task
}

// required by the protocol
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    // we don't report the progress of a download in our UI, but this is a cool method to do that with
}

// not required by the protocol, but we should definitely catch errors here
// so that we can avoid crashes
// and also so that we can detect that download tasks are (might be) complete
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error && (session == self.flickrDownloadSession)) {
        NSLog(@"Flickr background download session failed: %@", error.localizedDescription);
        [self flickrDownloadTasksMightBeComplete];
    }
}

// this is "might" in case some day we have multiple downloads going on at once

- (void)flickrDownloadTasksMightBeComplete
{
    if (self.flickrDownloadBackgroundURLSessionCompletionHandler) {
        [self.flickrDownloadSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
            // we're doing this check for other downloads just to be theoretically "correct"
            //  but we don't actually need it (since we only ever fire off one download task at a time)
            // in addition, note that getTasksWithCompletionHandler: is ASYNCHRONOUS
            //  so we must check again when the block executes if the handler is still not nil
            //  (another thread might have sent it already in a multiple-tasks-at-once implementation)
            if (![downloadTasks count]) {  // any more Flickr downloads left?
                // nope, then invoke flickrDownloadBackgroundURLSessionCompletionHandler (if it's still not nil)
                void (^completionHandler)() = self.flickrDownloadBackgroundURLSessionCompletionHandler;
                self.flickrDownloadBackgroundURLSessionCompletionHandler = nil;
                if (completionHandler) {
                    completionHandler();
                }
            } // else other downloads going, so let them call this method when they finish
        }];
    }
}

@end

PhotosCDTVC

//
//  PhotosCDTVC.h
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "CoreDataTableViewController.h"

@interface PhotosCDTVC : CoreDataTableViewController

// generic Photo displaying CDTVC
// hook up fetchedResultsController to any Photo fetch request
// use @"Photo Cell" as your table view cell's reuse id
// will segue to showing the image in an ImageViewController

@end
//
//  PhotosCDTVC.m
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "PhotosCDTVC.h"
#import "Photo.h"
#import "ImageViewController.h"

@implementation PhotosCDTVC

#pragma mark - UITableViewDataSource

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Photo Cell"];
    
    Photo *photo = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = photo.title;
    cell.detailTextLabel.text = photo.subtitle;
    
    return cell;
}

#pragma mark - Navigation

- (void)prepareViewController:(id)vc
                     forSegue:(NSString *)segueIdentifer
                fromIndexPath:(NSIndexPath *)indexPath
{
    Photo *photo = [self.fetchedResultsController objectAtIndexPath:indexPath];

    // note that we don't check the segue identifier here
    // could easily imagine two different segues to ImageViewController from this class
    // for example, one might apply some sort of sepia tone or something
    // but for now, we only have this one segue, so we'll not check the segue identifier

    if ([vc isKindOfClass:[ImageViewController class]]) {
        ImageViewController *ivc = (ImageViewController *)vc;
        ivc.imageURL = [NSURL URLWithString:photo.imageURL];
        ivc.title = photo.title;
    }
}

// boilerplate
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    NSIndexPath *indexPath = nil;
    if ([sender isKindOfClass:[UITableViewCell class]]) {
        indexPath = [self.tableView indexPathForCell:sender];
    }
    [self prepareViewController:segue.destinationViewController
                       forSegue:segue.identifier
                  fromIndexPath:indexPath];
}

// boilerplate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    id detailvc = [self.splitViewController.viewControllers lastObject];
    if ([detailvc isKindOfClass:[UINavigationController class]]) {
        detailvc = [((UINavigationController *)detailvc).viewControllers firstObject];
        [self prepareViewController:detailvc
                           forSegue:nil
                      fromIndexPath:indexPath];
    }
}

@end

PhotosByPhotographerCDTVC

#import "PhotosCDTVC.h"
#import "Photographer.h"

// this class inherits the ability to display a Photo in its rows
// and the ability to navigate to show the Photo's image
// from it superclass PhotosCDTVC

@interface PhotosByPhotographerCDTVC : PhotosCDTVC

@property (nonatomic, strong) Photographer *photographer;

@end
//
//  PhotosByPhotographerCDTVC.m
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "PhotosByPhotographerCDTVC.h"

@implementation PhotosByPhotographerCDTVC

- (void)setPhotographer:(Photographer *)photographer
{
    _photographer = photographer;
    self.title = photographer.name;
    [self setupFetchedResultsController];
}

- (void)setupFetchedResultsController
{
    NSManagedObjectContext *context = self.photographer.managedObjectContext;
    
    if (context) {
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
        request.predicate = [NSPredicate predicateWithFormat:@"whoTook = %@", self.photographer];
        request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"title"
                                                                  ascending:YES
                                                                   selector:@selector(localizedStandardCompare:)]];
        
        self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                            managedObjectContext:context
                                                                              sectionNameKeyPath:nil
                                                                                       cacheName:nil];
    } else {
        self.fetchedResultsController = nil;
    }
}


@end

URLViewController

//
//  URLViewController.m
//  Photomania
//
//  Created by CS193p Instructor.
//  Copyright (c) 2013 Stanford University. All rights reserved.
//

#import "URLViewController.h"

@interface URLViewController ()
@property (weak, nonatomic) IBOutlet UITextView *urlTextView;
@end

@implementation URLViewController

- (void)setUrl:(NSURL *)url
{
    _url = url;
    [self updateUI];
}

- (void)viewDidLoad // updateUI here in case our url property was set before outlets loaded
{
    [super viewDidLoad];
    [self updateUI];
}

- (void)updateUI
{
    self.urlTextView.text = [self.url absoluteString];
}

@end

URL absoluteString-RelativePath

    NSURL *url = [NSURL URLWithString:@"http://www.baidu.com:8080/search?id=1"];

    NSLog(@"scheme:%@", [url scheme]); //协议 http

    NSLog(@"host:%@", [url host]);     //域名 www.baidu.com
    NSLog(@"RelativePath: %@", [url relativePath]);
    NSLog(@"absoluteString:%@", [url absoluteString]);
    NSLog(@"path :%@",[url path]);
    NSLog(@"port: %@", [url port]);
    NSLog(@"query: %@", [url query]);

2013-12-14 13:28:15.454 PhotoMania[1596:70b] scheme:http

2013-12-14 13:28:15.456 PhotoMania[1596:70b] host:www.baidu.com

2013-12-14 13:28:15.456 PhotoMania[1596:70b] RelativePath: /search

2013-12-14 13:28:15.457 PhotoMania[1596:70b] absoluteString:http://www.baidu.com:8080/search?id=1

2013-12-14 13:28:15.457 PhotoMania[1596:70b] path :/search

2013-12-14 13:28:15.458 PhotoMania[1596:70b] port: 8080

2013-12-14 13:28:15.458 PhotoMania[1596:70b] query: id=1

 

Fetch Photo From Flickr Demo-TableView-Ipad

//
//  FetchViewController.m
//  FlickrFetch
//
//  Created by xushao on 13-12-11.
//  Copyright (c) 2013年 TGG. All rights reserved.
//

#import "FetchViewController.h"
#import "FlickrFetcher.h"
#import "ImageViewController.h"

@interface FetchViewController ()

@end

@implementation FetchViewController





-(void)setPhotos:(NSArray *)photos
{
    _photos = photos;
    [self.tableView reloadData];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [self.photos count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Fetch Photo Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    NSDictionary *photo = self.photos[indexPath.row];
    cell.textLabel.text = [photo valueForKeyPath:FLICKR_PHOTO_TITLE];
    cell.detailTextLabel.text = [photo valueForKeyPath:FLICKR_PHOTO_DESCRIPTION];
    
    return cell;
}

#pragma mark - Navigation

-(void)prepareImageViewController: (ImageViewController *)dvc
                        withphoto:(NSDictionary *)photo{
    dvc.imageURL = [FlickrFetcher URLforPhoto:photo format:FlickrPhotoFormatOriginal];
    dvc.title = [photo valueForKeyPath:FLICKR_PHOTO_TITLE];
}


// In a story board-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
    NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
    if(indexPath){
    if ([segue.identifier isEqualToString:@"Show Photo Segue"]) {
        if ([segue.destinationViewController isKindOfClass:[ImageViewController class]]) {
            ImageViewController *dvc = (ImageViewController *)segue.destinationViewController;
            [self prepareImageViewController:dvc withphoto:self.photos[indexPath.row]];
        }
    }
    }
}

-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
 
    id detail = self.splitViewController.viewControllers[1];
    
    if ([detail isKindOfClass:[UINavigationController class]]) {
        detail = [((UINavigationController *)detail).viewControllers firstObject];
        
    }
    if([detail isKindOfClass:[ImageViewController class]]){
    [self prepareImageViewController:detail withphoto:self.photos[indexPath.row]];
}
}


/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return NO if you do not want the specified item to be editable.
    return YES;
}
*/

/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }   
    else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }   
}
*/

/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/

/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return NO if you do not want the item to be re-orderable.
    return YES;
}
*/





@end

//
//  PostedFetchVC.m
//  FlickrFetch
//
//  Created by xushao on 13-12-12.
//  Copyright (c) 2013年 TGG. All rights reserved.
//

#import "PostedFetchVC.h"
#import "FlickrFetcher.h"

@interface PostedFetchVC ()

@end

@implementation PostedFetchVC

-(void)viewDidLoad
{
    [super viewDidLoad];
    [self fetchPhotos];
}

-(void )fetchPhotos
{
    NSURL *url = [FlickrFetcher URLforRecentGeoreferencedPhotos];
    
    NSData *JsonResults =  [NSData dataWithContentsOfURL:url];
    NSDictionary *porpertyList = [NSJSONSerialization JSONObjectWithData:JsonResults options:0 error:NULL];
    NSArray *photos = [porpertyList valueForKeyPath:FLICKR_RESULTS_PHOTOS];
    self.photos = photos;
    
}


@end

//
//  FlickrFetcher.h
//
//  Created for Stanford CS193p Fall 2013.
//  Copyright 2013 Stanford University. All rights reserved.
//

#import 

// key paths to photos or places at top-level of Flickr results
#define FLICKR_RESULTS_PHOTOS @"photos.photo"
#define FLICKR_RESULTS_PLACES @"places.place"

// keys (paths) to values in a photo dictionary
#define FLICKR_PHOTO_TITLE @"title"
#define FLICKR_PHOTO_DESCRIPTION @"description._content"
#define FLICKR_PHOTO_ID @"id"
#define FLICKR_PHOTO_OWNER @"ownername"
#define FLICKR_PHOTO_UPLOAD_DATE @"dateupload" // in seconds since 1970
#define FLICKR_PHOTO_PLACE_ID @"place_id"

// keys (paths) to values in a places dictionary (from TopPlaces)
#define FLICKR_PLACE_NAME @"_content"
#define FLICKR_PLACE_ID @"place_id"

// keys applicable to all types of Flickr dictionaries
#define FLICKR_LATITUDE @"latitude"
#define FLICKR_LONGITUDE @"longitude"
#define FLICKR_TAGS @"tags"

typedef enum {
	FlickrPhotoFormatSquare = 1,    // thumbnail
	FlickrPhotoFormatLarge = 2,     // normal size
	FlickrPhotoFormatOriginal = 64  // high resolution
} FlickrPhotoFormat;

@interface FlickrFetcher : NSObject

+ (NSURL *)URLforTopPlaces;

+ (NSURL *)URLforPhotosInPlace:(id)flickrPlaceId maxResults:(int)maxResults;

+ (NSURL *)URLforPhoto:(NSDictionary *)photo format:(FlickrPhotoFormat)format;

+ (NSURL *)URLforRecentGeoreferencedPhotos;

+ (NSURL *)URLforInformationAboutPlace:(id)flickrPlaceId;

+ (NSString *)extractNameOfPlace:(id)placeId fromPlaceInformation:(NSDictionary *)place;
+ (NSString *)extractRegionNameFromPlaceInformation:(NSDictionary *)placeInformation;

@end

//
//  FlickrFetcher.m
//
//  Created for Stanford CS193p Fall 2013.
//  Copyright 2013 Stanford University. All rights reserved.
//

#import "FlickrFetcher.h"
#import "FlickrAPIKey.h"

@implementation FlickrFetcher

+ (NSURL *)URLForQuery:(NSString *)query
{
    query = [NSString stringWithFormat:@"%@&format=json&nojsoncallback=1&api_key=%@", query, FlickrAPIKey];
    query = [query stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    return [NSURL URLWithString:query];
}

+ (NSURL *)URLforTopPlaces

{
    return [self URLForQuery:@"http://api.flickr.com/services/rest/?method=flickr.places.getTopPlacesList&place_type_id=7"];
}

+ (NSURL *)URLforPhotosInPlace:(id)flickrPlaceId maxResults:(int)maxResults;
{
    return [self URLForQuery:[NSString stringWithFormat:@"http://api.flickr.com/services/rest/?method=flickr.photos.search&place_id=%@&per_page=%d&extras=original_format,tags,description,geo,date_upload,owner_name,place_url", flickrPlaceId, maxResults]];
}

+ (NSURL *)URLforRecentGeoreferencedPhotos;
{
    return [self URLForQuery:[NSString stringWithFormat:@"http://api.flickr.com/services/rest/?method=flickr.photos.search&license=1,2,4,7&has_geo=1&extras=original_format,description,geo,date_upload,owner_name"]];
}

+ (NSString *)urlStringForPhoto:(NSDictionary *)photo format:(FlickrPhotoFormat)format
{
	id farm = [photo objectForKey:@"farm"];
	id server = [photo objectForKey:@"server"];
	id photo_id = [photo objectForKey:@"id"];
	id secret = [photo objectForKey:@"secret"];
	if (format == FlickrPhotoFormatOriginal) secret = [photo objectForKey:@"originalsecret"];
    
	NSString *fileType = @"jpg";
	if (format == FlickrPhotoFormatOriginal) fileType = [photo objectForKey:@"originalformat"];
	
	if (!farm || !server || !photo_id || !secret) return nil;
	
	NSString *formatString = @"s";
	switch (format) {
		case FlickrPhotoFormatSquare:    formatString = @"s"; break;
		case FlickrPhotoFormatLarge:     formatString = @"b"; break;
		case FlickrPhotoFormatOriginal:  formatString = @"o"; break;
	}
    
	return [NSString stringWithFormat:@"http://farm%@.static.flickr.com/%@/%@_%@_%@.%@", farm, server, photo_id, secret, formatString, fileType];
}

+ (NSURL *)URLforPhoto:(NSDictionary *)photo format:(FlickrPhotoFormat)format;
{
    return [NSURL URLWithString:[self urlStringForPhoto:photo format:format]];
}

+ (NSURL *)URLforInformationAboutPlace:(id)flickrPlaceId
{
    return [self URLForQuery:[NSString stringWithFormat:@"http://api.flickr.com/services/rest/?method=flickr.places.getInfo&place_id=%@", flickrPlaceId]];
}

#define FLICKR_PLACE_NEIGHBORHOOD_NAME @"place.neighbourhood._content"
#define FLICKR_PLACE_NEIGHBORHOOD_PLACE_ID @"place.neighbourhood.place_id"
#define FLICKR_PLACE_LOCALITY_NAME @"place.locality._content"
#define FLICKR_PLACE_LOCALITY_PLACE_ID @"place.locality.place_id"
#define FLICKR_PLACE_REGION_NAME @"place.region._content"
#define FLICKR_PLACE_REGION_PLACE_ID @"place.region.place_id"
#define FLICKR_PLACE_COUNTY_NAME @"place.county._content"
#define FLICKR_PLACE_COUNTY_PLACE_ID @"place.county.place_id"
#define FLICKR_PLACE_COUNTRY_NAME @"place.country._content"
#define FLICKR_PLACE_COUNTRY_PLACE_ID @"place.country.place_id"
#define FLICKR_PLACE_REGION @"place.region"

+ (NSString *)extractNameOfPlace:(id)placeId fromPlaceInformation:(NSDictionary *)place
{
    NSString *name = nil;
    
    if ([placeId isEqualToString:[place valueForKeyPath:FLICKR_PLACE_NEIGHBORHOOD_PLACE_ID]]) {
        name = [place valueForKeyPath:FLICKR_PLACE_NEIGHBORHOOD_NAME];
    } else if ([placeId isEqualToString:[place valueForKeyPath:FLICKR_PLACE_LOCALITY_PLACE_ID]]) {
        name = [place valueForKeyPath:FLICKR_PLACE_LOCALITY_NAME];
    } else if ([placeId isEqualToString:[place valueForKeyPath:FLICKR_PLACE_COUNTY_PLACE_ID]]) {
        name = [place valueForKeyPath:FLICKR_PLACE_COUNTY_NAME];
    } else if ([placeId isEqualToString:[place valueForKeyPath:FLICKR_PLACE_REGION_PLACE_ID]]) {
        name = [place valueForKeyPath:FLICKR_PLACE_REGION_NAME];
    } else if ([placeId isEqualToString:[place valueForKeyPath:FLICKR_PLACE_COUNTRY_PLACE_ID]]) {
        name = [place valueForKeyPath:FLICKR_PLACE_COUNTRY_NAME];
    }
    
    return name;
}

+ (NSString *)extractRegionNameFromPlaceInformation:(NSDictionary *)place
{
    return [place valueForKeyPath:FLICKR_PLACE_REGION_NAME];
}

@end

iOS json

作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式。

有的json代码格式比较混乱,可以使用此“http://www.bejson.com/”网站来进行JSON格式化校验(点击打开链接)。此网站不仅可以检测Json代码中的错误,而且可以以视图形式显示json中的数据内容,很是方便。

从IOS5开始,APPLE提供了对json的原生支持(NSJSONSerialization),但是为了兼容以前的ios版本,可以使用第三方库来解析Json。

本文将介绍TouchJson、 SBJson 、JSONKit 和 iOS5所支持的原生的json方法,解析国家气象局API,TouchJson和SBJson需要下载他们的库

TouchJson包下载: http://download.csdn.net/detail/enuola/4523169

SBJson 包下载: http://download.csdn.net/detail/enuola/4523177

JSONKit包下载:http://download.csdn.net/detail/enuola/4523160

下面的完整程序源码包下载:http://download.csdn.net/detail/enuola/4523223

PS:

国家气象局提供的天气预报接口

接口地址有三个:

http://www.weather.com.cn/data/sk/101010100.html

http://www.weather.com.cn/data/cityinfo/101010100.html

http://m.weather.com.cn/data/101010100.html

第三接口信息较为详细,提供的是6天的天气,关于API所返回的信息请见开源免费天气预报接口API以及全国所有地区代码!!(国家气象局提供),全国各城市对应这一个id号,根据改变id好我们就可以解析出来各个城市对应天气;

下面介绍四种方法解析JSON:

首先建立一个新的工程,(注意不要选择ARC机制)添加如下控件:

如上图所示。下面展出程序代码:

文件 ViewController.h 中:

[cpp] view plaincopy
#import

@interface ViewController : UIViewController

@property (retain, nonatomic) IBOutlet UITextView *txtView;

– (IBAction)btnPressTouchJson:(id)sender;
– (IBAction)btnPressSBJson:(id)sender;
– (IBAction)btnPressIOS5Json:(id)sender;
– (IBAction)btnPressJsonKit:(id)sender;

@end
文件ViewController.m中主要代码:
(1)使用TouchJSon解析方法:(需导入包:#import “TouchJson/JSON/CJSONDeserializer.h”)

//使用TouchJson来解析北京的天气  
- (IBAction)btnPressTouchJson:(id)sender {  
    //获取API接口  
    NSURL *url = [NSURL URLWithString:@"http://m.weather.com.cn/data/101010100.html"];  
    //定义一个NSError对象,用于捕获错误信息  
    NSError *error;  
    NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];  
    NSLog(@"jsonString--->%@",jsonString);  
    //将解析得到的内容存放字典中,编码格式为UTF8,防止取值的时候发生乱码  
    NSDictionary *rootDic = [[CJSONDeserializer deserializer] deserialize:[jsonString dataUsingEncoding:NSUTF8StringEncoding] error:&error];  
    //因为返回的Json文件有两层,去第二层内容放到字典中去  
    NSDictionary *weatherInfo = [rootDic objectForKey:@"weatherinfo"];  
    NSLog(@"weatherInfo--->%@",weatherInfo);  
    //取值打印  
    txtView.text = [NSString stringWithFormat:@"今天是 %@  %@  %@  的天气状况是:%@  %@ ",[weatherInfo objectForKey:@"date_y"],[weatherInfo objectForKey:@"week"],[weatherInfo objectForKey:@"city"], [weatherInfo objectForKey:@"weather1"], [weatherInfo objectForKey:@"temp1"]];  
      
}  

(2)使用SBJson解析方法:(需导入包:#import “SBJson/SBJson.h”)
//使用SBJson解析南阳的天气

- (IBAction)btnPressSBJson:(id)sender {  
    NSURL *url = [NSURL URLWithString:@"http://m.weather.com.cn/data/101180701.html"];  
    NSError *error = nil;  
    NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];  
    SBJsonParser *parser = [[SBJsonParser alloc] init];  
      
    NSDictionary *rootDic = [parser objectWithString:jsonString error:&error];  
    NSDictionary *weatherInfo = [rootDic objectForKey:@"weatherinfo"];  
    txtView.text = [NSString stringWithFormat:@"今天是 %@  %@  %@  的天气状况是:%@  %@ ",[weatherInfo objectForKey:@"date_y"],[weatherInfo objectForKey:@"week"],[weatherInfo objectForKey:@"city"], [weatherInfo objectForKey:@"weather1"], [weatherInfo objectForKey:@"temp1"]];  
}  


(3)使用IOS5自带解析类NSJSONSerialization方法解析:(无需导入包,IOS5支持,低版本IOS不支持)

- (IBAction)btnPressIOS5Json:(id)sender {  
      
    NSError *error;  
    //加载一个NSURL对象  
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://m.weather.com.cn/data/101180601.html"]];  
    //将请求的url数据放到NSData对象中  
    NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];  
    //IOS5自带解析类NSJSONSerialization从response中解析出数据放到字典中  
    NSDictionary *weatherDic = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&error];  
    NSDictionary *weatherInfo = [weatherDic objectForKey:@"weatherinfo"];  
    txtView.text = [NSString stringWithFormat:@"今天是 %@  %@  %@  的天气状况是:%@  %@ ",[weatherInfo objectForKey:@"date_y"],[weatherInfo objectForKey:@"week"],[weatherInfo objectForKey:@"city"], [weatherInfo objectForKey:@"weather1"], [weatherInfo objectForKey:@"temp1"]];  
    NSLog(@"weatherInfo字典里面的内容为--》%@", weatherDic );  
}  

(4)使用JSONKit的解析方法:(需导入包:#import “JSONKit/JSONKit.h”)

- (IBAction)btnPressJsonKit:(id)sender {  
      
    //如果json是“单层”的,即value都是字符串、数字,可以使用objectFromJSONString  
    NSString *json1 = @"{\"a\":123, \"b\":\"abc\"}";  
    NSLog(@"json1:%@",json1);  
    NSDictionary *data1 = [json1 objectFromJSONString];  
    NSLog(@"json1.a:%@",[data1 objectForKey:@"a"]);  
    NSLog(@"json1.b:%@",[data1 objectForKey:@"b"]);  
    [json1 release];  
      
    //如果json有嵌套,即value里有array、object,如果再使用objectFromJSONString,程序可能会报错(测试结果表明:使用由网络或得到的php/json_encode生成的json时会报错,但使用NSString定义的json字符串时,解析成功),最好使用objectFromJSONStringWithParseOptions:  
    NSString *json2 = @"{\"a\":123, \"b\":\"abc\", \"c\":[456, \"hello\"], \"d\":{\"name\":\"张三\", \"age\":\"32\"}}";  
    NSLog(@"json2:%@", json2);  
    NSDictionary *data2 = [json2 objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode];  
    NSLog(@"json2.c:%@", [data2 objectForKey:@"c"]);  
    NSLog(@"json2.d:%@", [data2 objectForKey:@"d"]);  
    [json2 release];  
}  

另外,由于iOS5新增了JSON解析的API,我们将其和其他五个开源的JSON解析库进行了解析速度的测试,下面是测试的结果。
我们选择的测试对象包含下面的这几个框架,其中NSJSONSerialization是iOS5系统新增的JSON解析的API,需要iOS5的环境,如果您在更低的版本进行测试,应该屏蔽相应的代码调用。

– [SBJSON (json-framework)](http://code.google.com/p/json-framework/)

– [TouchJSON (from touchcode)](http://code.google.com/p/touchcode/)

– [YAJL (objective-C bindings)](http://github.com/gabriel/yajl-objc)

– [JSONKit](https://github.com/johnezang/JSONKit)

– [NextiveJson](https://github.com/nextive/NextiveJson)

-[NSJSONSerialization](http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40010946)

我们选择了四个包含json格式的数据的文件进行测试。每一个文件进行100的解析动作,对解析的时间进行比较。

…..

测试的结果显示,系统的API的解析速度最快,我们在工程项目中选择使用,也是应用较为广泛的SBJSON的解析速度为倒数第二差,令我大跌眼镜。

与系统API较为接近的应该是JSONKit。

这里没有对API的开放接口和使用方式进行比较,若单纯基于以上解析速度的测试:

1:iOS5应该选择系统的API进行

2:不能使用系统API的应该选择JSONKit

core Data-Querying-NSPredicate

Creating an NSFetchRequest!

We’ll consider each of these lines of code one by one ...
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“Photo”];
request.fetchBatchSize = 20;
request.fetchLimit = 100;
request.sortDescriptors = @[sortDescriptor];
request.predicate = ...;

NSSortDescriptor

NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:@“title”
ascending:YES
selector:@selector(localizedStandardCompare:)];

NSPredicate

NSPredicate *predicate =
[NSPredicate predicateWithFormat:@“thumbnailURL contains %@”, serverName];
@“uniqueId = %@”, [flickrInfo objectForKey:@“id”] // unique a photo in the database
@“name contains[c] %@”, (NSString *) // matches name case insensitively!
@“viewed > %@”, (NSDate *) // viewed is a Date attribute in the data mapping!
@“whoTook.name = %@”, (NSString *) // Photo search (by photographer’s name)!
@“any photos.title contains %@”, (NSString *) // Photographer search (not a Photo search)!

NSCompoundPredicate…AND and OR

You can use AND and OR inside a predicate string, e.g. @“(name = %@) OR (title = %@)”!
Or you can combine NSPredicate objects with special NSCompoundPredicates.
NSArray *array = @[predicate1, predicate2];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:array];

Key Value Coding!

https://developer.apple.com/library/ios/documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html

Can actually do predicates like @“photos.@count > 5” (Photographers with more than 5 photos).! @count is a function (there are others) executed in the database itself.! .!
By the way, all this stuff (and more) works on dictionaries, arrays and sets too ...!
e.g. [propertyListResults valueForKeyPath:@“photos.photo.@avg.latitude”] on Flickr results!
returns the average latitude of all of the photos in the results (yes, really)!
e.g. @“photos.photo.title.length" would return an array of the lengths of the titles of the photos!

Putting it all together!

Let’s say we want to query for all Photographers ...!
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“Photographer”];
... who have taken a photo in the last 24 hours ...!
NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow:-24*60*60]; !
request.predicate = [NSPredicate predicateWithFormat:@“any photos.uploadDate > %@”, yesterday]; ! ... sorted by the Photographer’s name ...
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@“name” ascending:YES]];

Executing the fetch!

NSManagedObjectContext *context = aDocument.managedObjectContext; !
NSError *error; !
NSArray *photographers = [context executeFetchRequest:request error:&error]; !

Core Data- ios数据库

创建数据库文件,document

NSFileManager *fmgr =  [[NSFileManager defaultManager];
NSURL *documentDirectory = [[fmrg URLForDirectory:NSDocumentDirectory inDomains:NSUserDomainMark] FirstObject];

NSURL *url = [documentsDirectory URLByAppendingPathComponent:documentName];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];

This creates the UIManagedDocument instance, but does not open nor create the underlying file.

打开数据库或者创建数据库

BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];!
… if it does, open the document using ...!
[document openWithCompletionHandler:^(BOOL success) { /* block to execute when open */ }]; !
… if it does not, create the document using ...!
[document saveToURL:url // could (should?) use document.fileURL property here
forSaveOperation:UIDocumentSaveForCreating
competionHandler:^(BOOL success) { /* block to execute when create is done */ }];

check the documentState

- (void)documentIsReady
{
if (self.document.documentState == UIDocumentStateNormal) {
// start using document
}
}
Other documentStates!
UIDocumentStateClosed (you haven’t done the open or create yet)!
UIDocumentStateSavingError (success will be NO in completion handler)!
UIDocumentStateEditingDisabled (temporary situation, try again)!
UIDocumentStateInConflict (e.g., because some other device changed it via iCloud)!
We don’t have time to address these (you can ignore in homework), but know that they

Okay, document is ready to use, now what?!


Now you can get a managedObjectContext from it and use it to do Core Data stuff!!
- (void)documentIsReady
{
if (self.document.documentState == UIDocumentStateNormal) { !
NSManagedObjectContext *context = self.document.managedObjectContext;
// start doing Core Data stuff with context
}
} !
Okay, just a couple of more UIManagedDocument things before we start using that context …

NSNotification
How would you watch a document’s managedObjectContext?!



- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[center addObserver:self
selector:@selector(contextChanged:)
name:NSManagedObjectContextDidSaveNotification
object:document.managedObjectContext]; // don’t pass nil here!
}
- (void)viewWillDisappear:(BOOL)animated
{
[center removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:document.managedObjectContext];
[super viewWillDisappear:animated];
}

Okay, we have an NSManagedObjectContext, now what?!
Inserting objects into the database!

NSManagedObjectContext *context = aDocument.managedObjectContext;
NSManagedObject *photo =
[NSEntityDescription insertNewObjectForEntityForName:@“Photo”
inManagedObjectContext:context];
So how do I access my Entities’ Attributes with dot notation?!
// let’s create an instance of the Photo Entity in the database …!
NSManagedObjectContext *context = document.managedObjectContext; !
Photo *photo = [NSEntityDescription insertNewObjectForEntityForName:@“Photo” !
inManagedObjectContext:context];
// then set the attributes in our Photo using, say, an NSDictionary we got from Flickr …!
e.g. photo.title = [flickrData objectForKey:FLICKR_PHOTO_TITLE];!
// the information will automatically be saved (i.e. autosaved) into our document by Core Data
// now here’s some other things we could do too …!
NSString *myThumbnail = photo.thumbnailURL; !
photo.lastViewedDate = [NSDate date]; !
photo.whoTook = ...; // a Photographer object we created or got by querying!
photo.whoTook.name = @“CS193p Instructor”; // yes, multiple dots will follow relationships!

Universal Applications–ipad应用开发

20131210115539

How do I figure out “am I on an iPad?”!


BOOL iPad = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad); !
Use this sparingly!!

Never hide the left side (Master) behind a bar button



- (BOOL)splitViewController:(UISplitViewController *)sender
shouldHideViewController:(UIViewController *)master
inOrientation:(UIInterfaceOrientation)orientation
{
return NO; // never hide it
}

...

Hide Master in portrait orientation only (the default)
- (BOOL)splitViewController:(UISplitViewController *)sender
shouldHideViewController:(UIViewController *)master
inOrientation:(UIInterfaceOrientation)orientation
{
return UIInterfaceOrientationIsPortrait(orientation);
}

Split View helps you by providing that bar button


This gets called in your delegate when the master gets hidden …
- (void)splitViewController:(UISplitViewController *)sender
willHideViewController:(UIViewController *)master
withBarButtonItem:(UIBarButtonItem *)barButtonItem
forPopoverController:(UIPopoverController *)popover
{
barButtonItem.title = master.title;
// this next line would only work in the Detail
// and only if it was in a UINavigationController
self.navigationItem.leftBarButton = barButtonItem;
}

- (void)splitViewController:(UISplitViewController *)sender
willShowViewController:(UIViewController *)master
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
// this next line would only work in the Detail
// and only if it was in a UINavigationController
self.navigationItem.leftBarButton = nil;
}

Updating the Detail when the Master is touched?!

- (IBAction)doit
{
id detailViewController = self.splitViewController.viewControllers[1];
[detailViewController setSomeProperty:…]; // might want some Introspection first
}

Popovers

Example:!
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue isKindOfClass:[UIStoryboardPopoverSegue class]]) {
UIPopoverController *popoverController =
((UIStoryboardPopoverSegue *)segue).popoverController;
. . .
}
}

UISplitViewController


Accessing the Master and Detail MVCs from code!
All UIViewControllers know the UISplitViewController they are contained in (if in one):!
@property (strong) UISplitViewController *splitViewController; !
e.g. if (self.splitViewController) { /* I am in a UISplitViewController */ } !
!
The UISplitViewController has a property which is an array containing Master and Detail:!
@property (copy) NSArray *viewControllers; // index 0 is Master, 1 is Detail!
This property is not readonly, so you can change the Master & Detail of a Split View.!
The array is immutable though, so you must set both Master & Detail together at once.!
Usually you set this by ctrl-dragging in your storyboard though, not in code.!
!
e.g. A Master VC wants to get ahold of the Detail VC of the Split View both are in …!
UIViewController *detailVC = self.splitViewController.viewControllers[1]; !
If the Master VC is not in a Split View, this would nicely return nil.!

UITableViewController

cellForRowAtIndexPath:(NSIndexPath *)indexPath


- (UITableViewCell *)tableView:(UITableView *)sender
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
cell = [self.tableView dequeueReusableCellWithIdentifier:@“Flickr Photo Cell”
forIndexPath:indexPath];
cell.textLabel.text = [self getMyTitleForRow:indexPath.row inSection:indexPath.section];
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)sender; !
- (NSInteger)tableView:(UITableView *)sender numberOfRowsInSection:(NSInteger)section;
- (void)tableView:(UITableView *)sender didSelectRowAtIndexPath:(NSIndexPath *)path !
{ !
// go do something based on information about my Model!
// corresponding to indexPath.row in indexPath.section
}
- (void)tableView:(UITableView *)sender
accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
// Do something related to the row at indexPath,
// but not the primary action associated with touching the row
}

prepareForSegue

- (void):(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
// prepare segue.destinationController to display based on information
// about my Model corresponding to indexPath.row in indexPath.section
}

UITableView Spinner

UITableViewController has an “activity indicator” built in
You get it via this property in UITableViewController …
@property (strong) UIRefreshControl *refreshControl;
Start it with …
- (void)beginRefreshing;
Stop it with …
- (void)endRefreshing;
What if your Model changes?!
- (void)reloadData; !
Causes the table view to call numberOfSectionsInTableView: and numberOfRowsInSection:!
all over again and then cellForRowAtIndexPath: on each visible cell.!
Relatively heavyweight, but if your entire data structure changes, that’s what you need.!
If only part of your Model changes, there are lighter-weight reloaders, for example ...!
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths !
withRowAnimation:(UITableViewRowAnimation)animationStyle;