MKViewAnnotation custom annotations losing order when added in MKMapView - objective-c

I need to show on my MkMapView about 10 locations and respective custom annotations images (depending from the values loaded by a JSON parsing). As suggested in previous answers I have created a custom annotation class to store some data but, again, I cannot get the RIGHT ORDER: the custom images on each map locations don't respect the right sequence of respective parsed values, while in a UITableView its all perfect. This is the simplified code:
The example of correspondence:
if parsed valuesID is 100 ---> annotation image must be 100.png
if parsed valuesID is 200 ---> annotation image must be 200.png
if parsed valuesID is 300 ---> annotation image must be 300.png
The viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
map.showsUserLocation = true;
map.mapType = MKMapTypeStandard;
#define MakeLocation(lat,lon) [[CLLocation alloc] initWithLatitude:lat longitude:lon]
locations= #[ MakeLocation(lat1,lon1), MakeLocation(lat2,lon2), MakeLocation(lat3,lon3), MakeLocation(lat4,lon4), MakeLocation(lat5,lon5), MakeLocation(lat6,lon6), MakeLocation(lat7,lon7), MakeLocation(lat8,lon8), MakeLocation(lat9,lon9), MakeLocation(lat10,lon10) ];
}
The parseMethod called by a UIButton:
- (IBAction)parseMethod {
[map removeAnnotations:map.annotations];
// THE COMPLEX CODE TO PARSE VALUES of valuesID
...
... // so here I have the full array of valuesID
...
// THE CONTROL FOR THE END OF COMPLETE PARSING (blocks, cycle, ... )
[self addAnnotations]; // here i'm sure to call method AFTER THE END of complete parsing
}
The MyAnnotation2.h custom class:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface MyAnnotation2 : NSObject <MKAnnotation>
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, assign) int valuesIDMyAnnotation2;
#end
The MyAnnotation2.m custom class:
#import "MyAnnotation2.h"
#implementation MyAnnotation2
#synthesize coordinate;
#synthesize valuesIDMyAnnotation2;
#end
The addAnnotations method (called AFTER the COMPLETE END of parsing):
- (void)addAnnotations {
[table reloadData]; // UITableView with rows populated with locations coordinates and respective valuesID
[table scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
for (int l=0; l<[locations count]; l++) {
annotation2 = [[MyAnnotation2 alloc] init]; // create MyAnnotation2 istance to assign custom properties
annotation2.valuesIDMyAnnotation2 = [[valuesID objectAtIndex:l] intValue];
annotation2.coordinate = [locations[l] coordinate];
[map addAnnotation: annotation2]; // here we call delegate with all necessary data to add annotations, both location coordinate and corresponding valuesID
NSLog(#"%d - COORDINATES: %f - %f",annotation2.valuesIDMyAnnotation2,annotation2.coordinate.latitude, annotation2.coordinate.longitude);
}
}
The UITableView delegate:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:
UITableViewCellStyleSubtitle reuseIdentifier:#"Cell"];
}
cell.textLabel.text = [NSString stringWithFormat:#"%#",[coordinates objectAtIndex:indexPath.row]]; // here coordinates are values from each location
if ([[valuesID objectAtIndex:indexPath.row] intValue] == 100) {
UIImage *image = [UIImage imageNamed:#"100.png"];
[cell.imageView setImage:image];
}
if ([[valuesID objectAtIndex:indexPath.row] intValue] == 200) {
UIImage *image = [UIImage imageNamed:#"200.png"];
[cell.imageView setImage:image];
}
if ([[valuesID objectAtIndex:indexPath.row] intValue] == 300) {
UIImage *image = [UIImage imageNamed:#"300.png"];
[cell.imageView setImage:image];
}
return cell
}
Finally, the viewForAnnotation delegate:
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation {
if ( ! [annotation isKindOfClass:[MyAnnotation2 class]])
{
((MKUserLocation *)annotation).title = #"My position";
return nil;
}
MKAnnotationView *pinView= [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pinView"];
MyAnnotation2 *myPin = (MyAnnotation2 *)annotation;
if (myPin.valuesIDMyAnnotation2 == 100) {
pinView.image = [UIImage imageNamed:#"100.png"];
}
if (myPin.valuesIDMyAnnotation2 == 200) {
pinView.image = [UIImage imageNamed:#"200.png"];
}
if (myPin.valuesIDMyAnnotation2 == 300) {
pinView.image = [UIImage imageNamed:#"300.png"];
}
[pinView setFrame:CGRectMake(0, 0, 25, 25)];
return pinView;
}
EDIT - example of NSLogs results (code from addAnnotations method):
100 - COORDINATES lat1 - lon1 // here I expect annotation images100.png on location1
200 - COORDINATES lat2 - lon2 // ...
100 - COORDINATES lat3 - lon3
300 - COORDINATES lat4 - lon4
100 - COORDINATES lat5 - lon5
200 - COORDINATES lat6 - lon6
100 - COORDINATES lat7 - lon7
300 - COORDINATES lat8 - lon8
300 - COORDINATES lat9 - lon1
200 - COORDINATES lat10 - lon10
RESULTS:
On the UITableView its all PERFECT, I can see the right correspondence between locations coordinates and custom images, and also NSLog() gives the right correspondence of both location and valuesID. On the MKMapView, instead, custom annotation images are not added in the right sequence so I have right annotations images but in the wrong locations. Please, help me again to resolve this issue, thanks!

Put a break point on each of the pinView.image lines and when it is setting the image200.png, check what the coordinates are (you might need to NSLog them, I've never been great at digging deep in the debugger data). If you've got a mismatch there look through the rest of your code of anything else that could be changing the values of the locations, anything at all, and put a break point there. If that breakpoint gets triggered between the parseMethod and viewForAnnotation then you might have your culprit.

Related

Uislider in a tableView Cell

hi everyone i'm new in iOS programming ! I have a custom table view controller with custom table view cell ! one of those cell have a uislider and a label ! I want to change label text when slider change value ! this is my code :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *cellInfo = [[self.sections objectAtIndex:currentTab] objectAtIndex:indexPath.row];
HLNotificheCell *cell = [tableView dequeueReusableCellWithIdentifier:[cellInfo objectForKey:#"cell"] forIndexPath:indexPath];
UIImageView *radioIndicator = (UIImageView *)[cell.contentView viewWithTag:200];
radioIndicator.image = (currentBullet != indexPath.row) ? [UIImage imageNamed:#"RadioOff"] : [UIImage imageNamed:#"RadioOn"];
UIImageView *av = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 80)];
av.backgroundColor = [UIColor clearColor];
av.opaque = NO;
av.image = [UIImage imageNamed:#"NewsSeparetor.png"];
cell.backgroundView = av;
cell.slider.maximumValue = 100;
cell.slider.minimumValue = 1;
cell.slider.continuous = TRUE;
//set a method which will get called when a slider in a cell changes value
[cell.slider addTarget:self action:#selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
//Keep a reference to each slider by assigning a tag so that we can determine
//which slider is being changed
cell.slider.tag = indexPath.row;
//Grab the value from the sliderValuesArray and set the slider knob to that position
return cell;
}
-(void)sliderChanged:(UISlider*)sender{
HLNotificheCell *cell = [[HLNotificheCell alloc]init];
if (sender == cell.slider) {
cell.label.text = [NSString stringWithFormat:#"%0.3f", cell.slider.value];
}
}
Actually there is a lot of bad practices in your code. Please let me explain.
Let's begin with your HLNotificheCell class. I think header file should look like this:
#import <UIKit/UIKit.h>
#define HLNotificheCellIdentifier #"HLNotificheCellIdentifier"
#interface HLNotificheCell : UITableViewCell
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;
#property (strong, nonatomic) UISlider *slider;
#property (strong, nonatomic) UIImageView *radioIndicator;
#end
and implementation file:
#import "HLNotificheCell.h"
#implementation HLNotificheCell
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
if (self) {
_slider = [[UISlider alloc] init];
_slider.maximumValue = 100;
_slider.minimumValue = 1;
_slider.continuous = YES; //YES is more natural in objc rather than TRUE.
[self addSubview: _slider];
_radioIndicator = [[UIImageView alloc] init];
[self addSubview:_radioIndicator];
UIImageView *av = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 80)];
av.backgroundColor = [UIColor clearColor];
av.opaque = NO;
av.image = [UIImage imageNamed:#"NewsSeparetor.png"];
self.backgroundView = av;
//it's better to use built-in textLabel instead of creating your own. Trust me when you will have 20 different customized cells you will get lost with their names.
self.textLabel.textColor = [UIColor redColor];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
// layout your self.slider and self.radioIndicator here or use xib for it.
// e.g. this will layout slider to fit whole cell:
self.slider.frame = self.bounds;
}
#end
Ok, lets go now to cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// try to dequeue cell if exist
HLNotificheCell *cell = (HLNotificheCell *)[tableView dequeueReusableCellWithIdentifier:HLNotificheCellIdentifier];
// if doesn't, create new one.
if (!cell) { // is enough to set slider target only once when cell is created. When reuse is not needed.
cell = [[HLNotificheCell alloc] initWithReuseIdentifier:HLNotificheCellIdentifier];
[cell.slider addTarget:self action:#selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
}
//set image as you wish:
cell.radioIndicator.image = (currentBullet != indexPath.row) ? [UIImage imageNamed:#"RadioOff"] : [UIImage imageNamed:#"RadioOn"];
//Keep a reference to each slider by assigning a tag so that we can determine
//which slider is being changed
cell.slider.tag = indexPath.row;
//Grab the value from the sliderValuesArray and set the slider knob to that position
NSNumber *sliderValue = sliderValuesArray[indexPath.row];
[cell.slider setValue:sliderValue.floatValue animated:NO]
return cell;
}
and sliderChanged: method:
-(void)sliderChanged:(UISlider*)sender{
// You cannot do this:
// HLNotificheCell *cell = [[HLNotificheCell alloc]init];
// because you have to restore reference from sender.tag as you wrote in cellForRowAtIndexPath method:
HLNotificheCell *cell = (HLNotificheCell *)[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:slider.tag inSection:0]] // I assume you have only 1 section
cell.textLabel.text = [NSString stringWithFormat:#"%0.3f", cell.slider.value];
//save new value to the sliderValuesArray
self.sliderValuesArray[indexPath.row] = #(cell.slider.value);
}
Assumptions:
when you will use this part of code please do not use registerClass:forCellReuseIdentifier:
yours sliderValuesArray is kind of NSMutableArray class.
sliderValuesArray has been initialized with size same as number of cells, like:
self.sliderValuesArray = [[NSMutableArray alloc] initWithCapacity:<#numberOfCels#>];
for (int i = 0; i < sliderValuesArray.count; i++) {
sliderValuesArray[i] = #(0);
}
your table view contains only one type of cells (HLNotificheCell)
There could be some typos and/or lack of semicolons because I wrote it without compiler.
I doing this easier. Apple write that you can use IBActions for static rows. (You can read about it here in The Technique for Static Row Content. But I already tested it on iOS 9 with dynamic cells and it's just works :)
At first - Custom cell with IBAction
#interface SliderTableViewCell ()
#property (weak, nonatomic) IBOutlet UILabel *sliderValueLabel;
#property (weak, nonatomic) IBOutlet UISlider *slider;
#end
#implementation SliderTableViewCell
- (void)awakeFromNib {
self.slider.minimumValue = 1;
self.slider.maximumValue = 1000;
}
- (IBAction)sliderValueChanged:(id)sender {
self.sliderValueLabel.text = [NSString stringWithFormat:#"1_%.f", self.slider.value];
}
#end
Second - TableView Delegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
SliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kReuseIdentifierSliderCell];
cell.slider.value = 142;
cell.sliderValueLabel.text = #"1_142";
return cell;
}
Third - Run your app and enjoy yourself ;)

How to display an image in a CustomView based on a TableView

I'm new to Cocoa/Objective-c so please bear with me on this.
I'm trying to create a simple program that has a TableView on the left of the window and a CustomView on the right. The TableView has 3 columns: (NSString) name, (NSInteger) x, (NSInteger) y. The x and y values are randomly generated between 0 and 100 whenever a new row is added. I have a simple Star class to hold these three attributes. The TableView seems to be working just fine.
On the right, I have a CustomView where I want to load an image whenever a new row is added to the tableview at the x,y for that entry.
I created this "addStar" method in the CustomView class, which is an NSView. Then, in my TableViewController class, I'm trying to call the "addStar" method in my "add" method that adds a new row.
When I launch the program, I'm able to add items to the TableView, but nothing shows up in the CustomView. Any ideas of where I'm going wrong here?
CustomView.m
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSBezierPath* path = [NSBezierPath bezierPathWithRect:self.bounds];
[[NSColor whiteColor] setFill];
[path fill];
}
- (void)addStar:(float)x and:(float)y
{
NSRect rect = NSMakeRect(x, y, 35, 35);
imageView = [[NSImageView alloc] initWithFrame:rect];
[imageView setImageScaling:NSScaleNone];
[imageView setImage:[NSImage imageNamed:#"star.png"]];
[self addSubview:imageView];
}
#end
TableViewController.m
#import "TableViewController.h"
#import "CustomView.h"
#import "Star.h"
#implementation TableViewController
- (id)init
{
self = [super init];
if (self) {
list = [[NSMutableArray alloc] init];
}
return self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [list count];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
Star *s = [list objectAtIndex:row];
NSString *identifier = [tableColumn identifier];
return [s valueForKey:identifier];
}
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
Star *s = [list objectAtIndex:row];
NSString *identifier = [tableColumn identifier];
[s setValue:object forKey:identifier];
}
// Add row to the tableview
- (IBAction)add:(id)sender {
Star *star = [[Star alloc] init];
[list addObject:star];
[tableView reloadData];
// add star to the custom view
CustomView* cv = [[CustomView alloc] init];
[cv addStar:(float)[star xCoordinate] and:(float)[star yCoordinate]];
[star release];
}
- (IBAction)remove:(id)sender {
NSInteger row = [tableView selectedRow];
[tableView abortEditing];
if (row != -1)
[list removeObjectAtIndex:row];
[tableView reloadData];
}
- (void)dealloc
{
[list release];
[super dealloc];
}
#end
On right side you have Custom view And also you are creating new one as below...
CustomView* cv = [[CustomView alloc] init];
[cv addStar:(float)[star xCoordinate] and:(float)[star yCoordinate]];
But you are not adding this cv object on the screen.
write,
[self addSubview:cv];//add this below above code as you are creating new object each time.

MapKit: annotation views don't display until scrolling the map

I expect once clicking the toolbar button like "cafe", cafes will show in the visible map region. Data are fetched from Google Places API. Everything works fine except pins don't display after clicking the button.
A latency is expected here for sure. Yet i do the fetch in a background queue and puts up a spinning wheel while waiting, and the spinning wheel hides when fetching and parsing is done. So I am quite sure the data is there at the moment that spinning wheel disappears. But the pins don't show up until I scroll the map.
I figure that scrolling map only triggers mapView:regionDidChangeAnimated:. But I can't figure out how it relates the problem. Can anyone help?
The source code of ViewController.m, where pretty much everything happens.
#import "ViewController.h"
#import "MapPoint.h"
#import "MBProgressHUD.h"
#define kGOOGLE_API_KEY #"AIzaSyCHqbAoY7WCL3l7x188ZM4ciiTixejzQ4Y"
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#interface ViewController () <CLLocationManagerDelegate, MKMapViewDelegate>
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (strong, nonatomic) CLLocationManager *locationManager;
#property int currentDist;
#property CLLocationCoordinate2D currentCentre;
#end
#implementation ViewController
#synthesize mapView = _mapView;
#synthesize locationManager = _locationManager;
#synthesize currentDist = _currentDist;
#synthesize currentCentre = _currentCentre;
- (void)viewDidLoad{
[super viewDidLoad];
}
- (void)viewDidUnload{
[self setMapView:nil];
[super viewDidUnload];
}
// set the map region after launching
-(void)viewWillAppear:(BOOL)animated
{
//Instantiate a location object.
self.locationManager = [[CLLocationManager alloc] init];
//Make this controller the delegate for the location manager.
self.locationManager.delegate = self;
//Set some parameters for the location object.
[self.locationManager setDistanceFilter:kCLDistanceFilterNone];
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
// order: latitude(纬度), longitude(经度)
CLLocationCoordinate2D center = self.locationManager.location.coordinate;
// 单位是degree
MKCoordinateSpan span = MKCoordinateSpanMake(0.03, 0.03);
MKCoordinateRegion region = MKCoordinateRegionMake(center, span);
[self.mapView setRegion:region animated:YES];
// NSLog(#"currentCentre is (%f , %f)", self.currentCentre.latitude, self.currentCentre.longitude);
}
// Get place tpye from button title
// All buttons share this one method
- (IBAction)toolbarButtonPressed:(id)sender
{
UIBarButtonItem *button = (UIBarButtonItem *)sender;
NSString *buttonTitle = [button.title lowercaseString];
//Use this title text to build the URL query and get the data from Google.
[self queryGooglePlaces:buttonTitle];
}
// Parse response JSON data
-(void)parseData:(NSData *)responseData {
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions
error:&error];
//The results from Google will be an array obtained from the NSDictionary object with the key "results".
NSArray* places = [json objectForKey:#"results"];
[self plotPositions:places];
NSLog(#"Plot is done");
}
// Format query string
-(void) queryGooglePlaces: (NSString *) googleType {
// query string
NSString *url = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/search/json?location=%f,%f&radius=%#&types=%#&sensor=true&key=%#", self.currentCentre.latitude, self.currentCentre.longitude, [NSString stringWithFormat:#"%i", _currentDist], googleType, kGOOGLE_API_KEY];
//string to URL
NSURL *googleRequestURL=[NSURL URLWithString:url];
// Retrieve data from the query URL by GCD
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL: googleRequestURL];
[self parseData:data];
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Please Wait..";
}
#pragma mark - Map View Delegate
// called many times when map scrolling or zooming
// Use this to get currentCentre and currentDist (radius)
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
//Get the east and west points on the map so you can calculate the distance (zoom level) of the current map view.
MKMapRect mRect = self.mapView.visibleMapRect;
MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));
//Set your current distance instance variable.
self.currentDist = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);
//Set your current center point on the map instance variable.
self.currentCentre = self.mapView.centerCoordinate;
// NSLog(#"currentCentre is (%f , %f)", self.currentCentre.latitude, self.currentCentre.longitude);
}
// Setup annotation objects
-(void)plotPositions:(NSArray *)data {
// 1 - Remove any existing custom annotations but not the user location blue dot.
for (id<MKAnnotation> annotation in self.mapView.annotations) {
if ([annotation isKindOfClass:[MapPoint class]]) {
[self.mapView removeAnnotation:annotation];
}
}
// 2 - Loop through the array of places returned from the Google API.
for (int i=0; i<[data count]; i++) {
//Retrieve the NSDictionary object in each index of the array.
NSDictionary* place = [data objectAtIndex:i];
// 3 - There is a specific NSDictionary object that gives us the location info.
NSDictionary *geo = [place objectForKey:#"geometry"];
// Get the lat and long for the location.
NSDictionary *loc = [geo objectForKey:#"location"];
// 4 - Get your name and address info for adding to a pin.
NSString *name=[place objectForKey:#"name"];
NSString *vicinity=[place objectForKey:#"vicinity"];
// Create a special variable to hold this coordinate info.
CLLocationCoordinate2D placeCoord;
// Set the lat and long.
placeCoord.latitude=[[loc objectForKey:#"lat"] doubleValue];
placeCoord.longitude=[[loc objectForKey:#"lng"] doubleValue];
// 5 - Create a new annotation.
MapPoint *placeObject = [[MapPoint alloc] initWithName:name address:vicinity coordinate:placeCoord];
[self.mapView addAnnotation:placeObject];
}
NSLog(#"addAnnotation is done");
}
// Setup annotation view
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
// Define your reuse identifier.
static NSString *identifier = #"MapPoint";
if ([annotation isKindOfClass:[MapPoint class]]) {
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.animatesDrop = YES;
// NSLog(#"annotation view is added");
return annotationView;
}
return nil;
}
#end
A couple of things:
Move your removeAnnotation and addAnnotation code to run on the UI thread, e.g.:
dispatch_async (dispatch_get_main_queye(), ^
{
[self.mapView addAnnotation:placeObject];
});
Move your viewWillAppear initialization code to viewDidLoad. viewWillAppear may be called multiple times during the lifetime of your view controller

Sizing a Custom UITableViewCell and included UIImageView based on Image Size

I am trying to customize the UITableViewCell below for an iPhone app in a grouped table view. What I would like to do is have the image width take up the whole cell minus padding (280) and the height variable based on the image size.
Currently I am using SDWebImage to asynchronously download remote images. This may not be the correct thing to do in this case. I am also having trouble figuring out how to give the custom cell the image on initialization. The image URL is stored in self.beerPhoto in the DetailViewController.
I have searched for this a number of ways and have not found exactly what I am looking for. The closest was this: How to scale a UIImageView proportionally, but this method seems to require the cell to have the image at initialization, as I tried to make this code work but setting the image after initialization left a blank cell.
The current code includes constants I set to approximate an image in portrait orientation. In reality some of the images are portrait and some are landscape orientation.
Please let me know if there's anything additional you need to know.
Header for custom UITableViewCell:
#import <UIKit/UIKit.h>
#interface BeerDetailHead : UITableViewCell {
UILabel *beerName;
UIImageView *beerImage;
}
#property(nonatomic, retain)UILabel *beerName;
#property(nonatomic, retain)UIImageView *beerImage;
#end
Relevant portion of implementation for custom UITableViewCell
#import "BeerDetailHead.h"
#implementation BeerDetailHead
#synthesize beerName, beerImage;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
//beerName = [[UILabel alloc]init];
//beerName.textAlignment = UITextAlignmentLeft;
//beerName.font = [UIFont systemFontOfSize:14];
beerImage = [[UIImageView alloc]init];
//[self.contentView addSubview:beerName];
[self.contentView addSubview:beerImage];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect contentRect = self.contentView.bounds;
CGFloat boundsX = contentRect.origin.x;
CGRect frame;
frame= CGRectMake(boundsX+10 ,10, 280, 375);
beerImage.frame = frame;
}
DetailViewController's cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
NSArray *listData =[self.tableContents objectForKey:
[self.sortedKeys objectAtIndex:[indexPath section]]];
NSLog(#"listData = %#", listData);
NSUInteger row = [indexPath row];
if ([self.sortedKeys objectAtIndex:[indexPath section]] == #"header"){
static NSString *headerTableIdentifier = #"HeaderTableIdentifier";
BeerDetailHead * headerCell = (BeerDetailHead*)[tableView
dequeueReusableCellWithIdentifier: headerTableIdentifier];
if(headerCell == nil) {
headerCell = [[[BeerDetailHead alloc] initWithFrame:CGRectZero reuseIdentifier:headerTableIdentifier] autorelease];
}
headerCell.beerName.text = [listData objectAtIndex:row];
[headerCell.beerImage setImageWithURL:[NSURL URLWithString:self.beerPhoto]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
//NSLog(#"frame = %#", headerCell.beerImage.frame);
return headerCell;
}
else{
//use standard UITableViewCell
}
}
Implement tableView:heightForRowAtIndexPath: delegate method and return calculated height for each row from this method.
After loading each image call reloadData on your tableView OR if you want to animate changes call:
[self.tableView beginUpdates];
[self.tableView endUpdates];
Also, you might want to combine several sequence height updates into one. I would use I little delay to perform this:
// triggerUpdates calls the above code
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(triggerUpdates) object:nil];
[self performSelector:#selector(triggerUpdates) withObject:nil afterDelay:0.1];

How can I display my images in my viewController the same as the below screen shot?

In my iPad program I have an array which holds images. How can I display my all images (from an image array) in my viewController the same as the below screen shot? Is there a good way to do it, any sample application paths to refer or sample code?
I need the following functionality.
Tapping an image will show it full screen
A close button to close this view as the same as the screen shot.
Two buttons to display the remaining and previous images.
A sample screen shot is attached below. We can see that the below application is showing all images at the right side of the screen.
Alright, this should be easy, though a bit of coding is needed.
Start out with a simple view based app template.
I don't know where the images come from so I just put all the images to be shown in the resources folder.
The view controller PictureViewController will accept an array of UIImages and should be presented modally. I'll post the code below.
The code to show the PictureViewController is placed in the view controller created by the template. I just added a simple UIButton to the view to trigger an action which looks something like this:
- (IBAction)onShowPictures
{
// Load all images from the bundle, I added 14 images, named 01.jpg ... 14.jpg
NSMutableArray *images = [NSMutableArray array];
for (int i = 1; i <= 14; i++) {
[images addObject:[UIImage imageNamed:[NSString stringWithFormat:#"%02d.jpg", i]]];
}
PictureViewController *picViewController = [[PictureViewController alloc] initWithImages:images];
[self presentModalViewController:picViewController animated:YES];
[picViewController release];
}
The view controller's view was created with interface builder, looking like this:
PictureViewController.xib
View Hierarchy
The fullscreen image is linked to an outlet fullScreenImage seen in the following header file. Actions are connected as well.
I've set the fullscreen imageView's content mode to Aspect Fit.
The Thumbnails will be added dynamically with code. Here's the view controller's code
PictureViewController.h
#interface PictureViewController : UIViewController
{
NSMutableArray *imageViews;
NSArray *images;
UIImageView *fullScreenImage;
int currentPage;
}
#property (nonatomic, retain) NSMutableArray *imageViews;
#property (nonatomic, retain) NSArray *images;
#property (nonatomic, retain) IBOutlet UIImageView *fullScreenImage;
- (id)initWithImages:(NSArray *)images;
- (IBAction)onClose;
- (IBAction)onNextThumbnails;
- (IBAction)onPreviousThumbnails;
#end
PictureViewController.m
The define MAX_THUMBNAILS on top defines the maximum of thumbnails seen. A UITapGestureRecognizer will take care of the tap events for the thumbnails. Adjust the CGRectMake in setupThumbnailImageViews to position the thumbnails as you wish. This controller is just a basic approach without orientation support.
#import "PictureViewController.h"
#define MAX_THUMBNAILS 6
#interface PictureViewController ()
- (void)showSelectedImageFullscreen:(UITapGestureRecognizer *)gestureRecognizer;
- (void)setupThumbnailImageViews;
- (void)setThumbnailsForPage:(int)aPage;
- (UIImage *)imageForIndex:(int)anIndex;
#end
#implementation PictureViewController
#synthesize imageViews, images, fullScreenImage;
- (id)initWithImages:(NSArray *)someImages
{
self = [super init];
if (self) {
self.images = someImages;
self.imageViews = [NSMutableArray array];
currentPage = 0;
}
return self;
}
- (void)viewDidLoad
{
self.fullScreenImage.image = [images objectAtIndex:0];
[self setupThumbnailImageViews];
[self setThumbnailsForPage:0];
}
- (void)showSelectedImageFullscreen:(UITapGestureRecognizer *)gestureRecognizer
{
UIImageView *tappedImageView = (UIImageView *)[gestureRecognizer view];
fullScreenImage.image = tappedImageView.image;
}
- (void)setupThumbnailImageViews
{
for (int i = 0; i < MAX_THUMBNAILS; i++) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(20.0,
166.0 + (130.0 * i),
130.0,
90.0)];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(showSelectedImageFullscreen:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
tapGestureRecognizer.numberOfTouchesRequired = 1;
[imageView addGestureRecognizer:tapGestureRecognizer];
[tapGestureRecognizer release];
imageView.userInteractionEnabled = YES;
[imageViews addObject:imageView];
[self.view addSubview:imageView];
}
}
- (void)setThumbnailsForPage:(int)aPage
{
for (int i = 0; i < MAX_THUMBNAILS; i++) {
UIImageView *imageView = (UIImageView *)[imageViews objectAtIndex:i];
UIImage *image = [self imageForIndex:aPage * MAX_THUMBNAILS + i];
if (image) {
imageView.image = image;
imageView.hidden = NO;
} else {
imageView.hidden = YES;
}
}
}
- (UIImage *)imageForIndex:(int)anIndex
{
if (anIndex < [images count]) {
return [images objectAtIndex:anIndex];
} else {
return nil;
}
}
#pragma mark -
#pragma mark user interface interaction
- (IBAction)onClose
{
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)onNextThumbnails
{
if (currentPage + 1 <= [images count] / MAX_THUMBNAILS) {
currentPage++;
[self setThumbnailsForPage:currentPage];
}
}
- (IBAction)onPreviousThumbnails
{
if (currentPage - 1 >= 0) {
currentPage--;
[self setThumbnailsForPage:currentPage];
}
}
#pragma mark -
#pragma mark memory management
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
[images release];
}
- (void)viewDidUnload
{
[super viewDidUnload];
[imageViews release];
[fullScreenImage release];
}
- (void)dealloc
{
[images release];
[imageViews release];
[fullScreenImage release];
[super dealloc];
}
#end
The result: