Bu uygulamada sizlere uzaktaki bir JSON kaynaktan çekilen POI (Point of Interest - ilgi çekici nokta) listesini öncelikle bir tabloda daha sonra da bir harita üzerinde nasıl göstereceğinizi anlatacağız. Örnek uygulamamıza göz atmadan önce aşağıdaki konuları incelemenizi tavsiye ederiz;
Uygulamamızda yine tasarımcımızdan gelen yönlendirmelere göre hareket edeceğiz ve tasarımcının bize bildirdiği yazıtipi ve görselleri kullanacağız.
Gördüğünüz gibi tasarımcımız genel görünümle beraber görsel öğelerin yüksekliklerini, ebatlarını ve bunlarla beraber yazıyiplerini ve büyüklüklerini detaylı bir şekilde anlatmış. Siz de projenizde profesyonel bir tasarımcıyla çalışıyorsanız, bu şekilde direktifler beklemeye hazır olun (göndermezse de göndermesini talep edin).
Bazı tasarımcılar PSD dosyalarının genel görünümünü PNG olarak kayıt edip özellikleri üzerine yazmayı tercih ederken, kimi tasarımcılar web üzerinde yardımcı sitelere genel tasarımı koyarak uygulama detaylarını anlatırlar. Metodlar farklılık gösterse de, geliştirici için önemli olan kodlarken uyulması gereken kurallardır ve boyut, renk RGB değeri, kullanılan yazıtipi gibi bilgileri tüm detaylarıyla bilmesi gerekir.
Bu uygulamamız için Xcode’da yeni bir proje oluşturup, Single View Application taslak projesini seçiyoruz. Projenin diğer özelliklerini girdikten sonra Xcode bize tek bir ViewController ve bununla ilgili bir Storyboard öğesi verecektir. Öncelikle Storyboard üzerinden ilk oluşturulan ekrana aşağıdaki öğeleri ekleyerek tasarımın kaba halini oluşturacağız;
- Map View
- Table View
- UIView
MKMapView ve UITableView yerleşimi ekranda birbirinin tam üzerine gelecek şekilde ayarlanacak ve böylece kullanıcı yukarıda hangi görünümü seçerse o ekran tasarımcının belirlediği alanda gösterilecektir. Uygulamanın üst tarafında bulunan uygulama logosu ve görüntü seçim düğmelerinin bulunduğu bölüm ise UIView komponenti içerisine aşağıdaki elemanlar eklenerek gerçekleniyor;
- UISegmentedControl
- UIImageView
Bu ekrandaki UIImageView tasarımcıdan gelen logonun görsel dosyasını göstermek için kullanılacak ve ekranın yatay eksende tam ortalanacağı durumda yer alacak. UISegmentedControl ise List ve Map adında iki adet düğmeyi barındıracak şekilde ayarlanır. Interface Builder içerisindeki öğelerin hiyerarşisine bakarsanız, yukarıya bıraktığımız UIView öğesinin alt öğeleri olduğunu görürsünüz. Burada kolaylık olması açısından logoyu taşıyacak UIImageView ve UISegmentedControl UIView içerisine alt öğe olarak sürüklendi. Böylece elemanlar UIView içerisinde yer aldılar.
Bölüm adlarını belirlemek için öncelikle Interface Builder’da Segmented Control seçildikten sonra sağ menüden dördüncü sekmeye (öğe özellikleri) gelerek Segments özelliğinden kaç adet bölüm olacağını belirlemeniz gerekir. Biz burada 2 (iki) değerini seçiyoruz. Daha sonra her bir bölüm (Segment) için Title özelliğini elle değiştirebiliriz.
UISegmentedControl içerisindeki rengi tasarımcının istediği renk haline getiriyoruz. Aynı sekme içerisinde Tile özelliğine gelerek buradan renk seçeneğini kendimiz belirlemek için Other Color seçeneğini tıklıyoruz ve karşımıza istediğimiz rengi oluşturabileceğimiz küçük bir sihirbaz geliyor. Bu sihirbazda yer alan büyüteç simgesine tıklayarak ekran üzerindeki herhangi bir rengi seçmeniz mümkündür. Biz de tasarımcının bize gönderdiği referans ekran görüntüsünden yeşil rengi seçerek istenilen rengi alabiliriz. Bu yöntemi diğer renk seçeneklerini oluştururken de kullanabilirsiniz.
Bu tanımları yaptıktan sonra proje özelliklerinden harita servisini aktif hale getiriyoruz ve MapKit Framework projemize dâhil edilmiş oluyor. Daha sonra ViewController.h dosyasında aşağıdaki tanımları gerçekleştiriyoruz.
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, MKMapViewDelegate>
- (IBAction) segmentClick:(id)sender;
@property (nonatomic, retain) IBOutlet MKMapView *poiMapView;
@property (nonatomic, retain) IBOutlet UITableView *poiTableView;
@property (nonatomic, retain) IBOutlet UISegmentedControl *segmentedControl;
@property (nonatomic, retain) NSMutableArray *poiArray;
@end
Burada UITableViewDataSource ve UITableViewDelegate protokolleri tablonun satırlarının oluşturulmasında ve verinin doldurulmasında bize yardımcı olacak metodları içeriyor. MKMapViewDelegate ise harita ile ilgili temsilci metodları kullanmamızı sağlıyor. poiMapView ve poiTableView değişkenleri sırasıyla haritayı ve tabloyu belirtiyor. poiArray ise bize web servisinden dönen JSON veri içerisindeki mekânları saklamamızı sağlayacak. Son olarak segmentClick: fonksiyonu ise UISegmentedControl içerisindeki Value Changed aksiyonu ile harekete geçerek mekân listesinin görüntülenme biçimini belirtiyor.
Şimdi segmentClick: metodunu yakından inceleyelim;
- (IBAction) segmentClick:(id)sender {
UISegmentedControl *sgmntControl = (UISegmentedControl *) sender;
switch (sgmntControl.selectedSegmentIndex) {
case 0:{ // Listele ekrani secildi
poiTableView.alpha = 1;
poiMapView.alpha = 0;
break;
}
case 1:{ // harita ekrani secildi
poiTableView.alpha = 0;
poiMapView.alpha = 1;
break;
}
default:
break;
}
[self loadData];
}
Bu metod kullanıcının haritayı mı yoksa listeyi mi açacağını belirlediğinden, kullanıcının seçimine göre harita ve tablonun alpha değerleriyle oynayarak hangisinin ekrana geleceğini belirleyeceğiz. Kullanıcı Map seçeneğini seçtiğinde poiTableView görünmez olacak ve poiMapView tam görünür hale getirilecek. List seçeneği seçildiğinde ise tersi gerçekleşecek ve harita görünmez olurken tablo ekranda görünür olacak. loadData metodu ise uzaktaki sunucuya bağlanarak en güncel listeyi kullanıcıya gösterecek.
loadData metodu ise aşağıdaki gibidir;
- (void) loadData {
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:REMOTE_POI_URL]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
SBJsonParser *jsonParser = [SBJsonParser new];
id pois = [jsonParser objectWithData:data];
[poiArray removeAllObjects];
for(id poi in pois) {
NSString *poiName = [poi objectForKey:@"name"];
double lat = [[poi objectForKey:@"lat"] doubleValue];
double lon = [[poi objectForKey:@"lon"] doubleValue];
[poiMapView addAnnotation:[[MyMapPin alloc] initWithName:poiName coordinate:CLLocationCoordinate2DMake(lat,lon)]];
[poiArray addObject:poi];
}
[poiTableView reloadData];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(poiMapView.userLocation.coordinate, 20000, 20000);
[poiMapView setRegion:region animated:YES];
}];
}
Bu metod içerisinde web servisi ile asenkron metodlar çağırarak, kullanıcının işlemini engellemeyecek şekilde bir İnternet bağlantısı açıp, mekân listesini indiriyoruz. Burada SBJsonParser yardımıyla servisten bize gelen cevap bir diziye dönüştürülüyor ve biz bu diziden gelen mekânları öncelikle poiMapView içerisine addAnnotation: metodu ile ekliyoruz. Böylece harita üzerinde pinler yerleştirilmiş oluyor. Tablomuz ise verileri poiArray dizi ile okuduğundan, addObject: metoduyla pinleri buraya ekliyoruz. İşimiz bittiğinde güncel veriyi görüntülemek için reloadData metodu ile tabloyu tekrar dolduruyoruz. Harita üzerinde kullanıcının konumunu göstermek içinse poiMapView içerisinde yer alan userLocation özelliğinden faydalanarak anlık konuma geçişi sağlıyoruz.
NOT: Mekân listesi ViewController.m dosyası içerisinde #define REMOTE_POI_URL @"http://bilmiyordum.com/poi.json" ile tanımlanmıştır. Her mekan için name mekan adını, lat enlemi, lon ise boylamı belirtir.
[{"name" : "Grand Canyon", "lat" : 36.195525, "lon" : -112.631836},{"name" : "London Eye", "lat" : 51.503267, "lon" : -0.120850},
{"name" : "Eiffel Tower", "lat" : 48.857769, "lon" : 2.293439},
{"name" : "Bosphorus", "lat" : 41, "lon" : 29},
{"name" : "Selimiye Camii", "lat" : 41.678105, "lon" : 26.558568},
{"name" : "Kremlin", "lat" : 55.751656, "lon" : 37.616844}]
Ekran ilk oluşturulduğunda tasarımcıdan gelen fontları UISegmentedControl öğesine tanıtmak içinse aşağıdaki metodları viewDidLoad içerisinde kullanıyoruz.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
poiArray = [NSMutableArray new];
UIFont *font = [UIFont fontWithName:@"MuseoSansRounded-500" size:12];
NSDictionary *attributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
[segmentedControl setTitleTextAttributes:attributes forState:UIControlStateNormal];
[self loadData];
}
UISegmentedControl öğesinin yazıtipini Museo Sans yapabilmek için setTitleTextAttributes: metodundan faydalanıyoruz. Bu metod NSDictionary tipinde bir değişkenle metinle ilgili çeşitli özellikler alıyor ve yazıtipi istediğimiz hale geliyor. loadData ise verileri ilk açılışta yüklememizi sağlıyor.
Şimdi de UITableViewDelegate ve UITableDataSource metodlarını inceleyelim;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return poiArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = @"PoiCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
cell.textLabel.font = [UIFont fontWithName:@"MuseoSansRounded-500" size:16];
cell.backgroundColor = [UIColor groupTableViewBackgroundColor];
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"list_arrow"]];
}
cell.textLabel.text = [poiArray[indexPath.row] objectForKey:@"name"];
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
segmentedControl.selectedSegmentIndex = 1;
poiTableView.alpha = 0.0f;
poiMapView.alpha = 1.0f;
id poi = [poiArray objectAtIndex:indexPath.row];
double lat = [[poi objectForKey:@"lat"] doubleValue];
double lon = [[poi objectForKey:@"lon"] doubleValue];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake(lat,lon), 20000, 20000);
[poiMapView setRegion:region animated:YES];
}
tableView:numberOfRowsInSection: metodu tablodaki satır sayısını poiArray ile belirtiyor. tableView:cellForRowAtIndexPath: metodunda ise her satır için oluşturulacak tasarım belirleniyor. Uygulamamızdaki renklere göre backgroundColor özelliğini, yazıtipine göre font özelliğini ve sağ ok için de accessoryView özelliğini değiştiriyoruz. Satırda gösterilecek değerse mekânın ismi oluyor.
Kullanıcı bir mekâna tıkladığında, o mekânın harita üzerinde büyütülmesini istediğimizden öncelikle tabloyu gizleyip haritayı görünür hale getiriyoruz. Büyütülmek istenen mekânın koordinatlarını poiArray içerisinden alıyoruz ve poiMapView ‘da setRegion:animated: metoduyla gösteriyoruz.
Son olarak standart pinleri tasarımda yer alan simgeleri değiştirmek için MKMapViewDelegate içerisindeki mapView:viewForAnnotation: metodunu kullanabiliriz. Tıpkı tablodaki her satırın görüntüsünü değişebilen tableView:cellForRowAtIndexPath: metodu gibi bu metod da her pin için ayrı bir görüntü oluşturabilmemizi sağlıyor. Bizim burada yapacağımız ise pinleri temsil eden MKPinAnnotationView nesnesindeki image özelliğini kendi görselimizle değiştirmek olacaktır.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *viewId = @"PoiPinView";
MKPinAnnotationView *annotationView = (MKPinAnnotationView*) [poiMapView dequeueReusableAnnotationViewWithIdentifier:viewId];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:viewId];
annotationView.image = [UIImage imageNamed:@"pin.png"];
}
return annotationView;
}
Listede bir elemana tıkladığınızda haritada o bölgenin görüntülendiğini ve pinin ortalandığını göreceksiniz.