OCA
18

iPhone Uygulamalarında Tablo (UITableView) Kullanımı

9 yorum | Kategori: Mobil Programlama | 18 Ocak 2011 Salı

iPhone programlama ile ilgili olan bu ikinci yazımda, iPhone uygulamaları geliştirirken en çok kullanılan bileşenlerden biri olan UITableView bileşeninin, yani tabloların nasıl kullanılacağını göstereceğim. Önce bir liste göstermekten başka bir şey yapmayan etkileşimsiz bir tabloyu çalışır hale getirip, sonrasında bu tablonun sırasıyla satırlarına basıldığında bir mesaj gösteren, satırları silinebilen ve satırlarının yerleri değiştirilebilen hale nasıl getirilebileceğini adım adım göstereceğim.

Başlamak için tahmin edebileceğiniz üzere XCode'u açıp, View-based application türünden yeni bir proje yaratmanız gerekiyor. Bunun nasıl yapılacağını önceki yazımda göstermiştim. Ben projeye yine önceki yazımda olduğu gibi Ornek adını verdim, dosya adlarının uyuşması açısından aynısını yapmanızı tavsiye ederim.

Projeyi yarattıktan sonra yapmanız gereken ilk şey, "OrnekViewController.xib" adlı arayüz dosyasını Interface Builder (IB) ile açıp arayüzü tasarlamak. İlk etapta ihtiyacınız olan tek şey bir tablo. Bunun için Library penceresinden bir TableView bileşenini arayüze sürükleyip bırakın. View'iniz üzerinde aşağıdaki şekilde gösterildiği gibi yer tutucu değerler ile dolu bir tablo göreceksiniz. Daha sonra aşağıdaki şekilde göstermiş olduğum gibi koyduğunuz bu tabloyu seçin ve Inspector penceresindeki dataSource ve delegate Outlet'lerini, .xib dosyasının içeriğini gösteren penceredeki File's Owner nesnesine bağlayın. Bağlama işleminin kontrol tuşuna basılı tutarken sürükleyip bırakma ile yapıldığını önceki yazımda da göstermiştim.

Ornek 2 Arayüzü

Şimdi XCode'a dönüp bu tabloya koddan erişebilmenizi sağlayacak olan Outlet'i tanımlamanız gerekiyor. Ayrıca tablonun göstereceği verileri içeren bir diziye de ihtiyacınız var. Bunun için "OrnekViewController.h" dosyasını aşağıdaki hale getirin.

#import <UIKit/UIKit.h>

@interface OrnekViewController : UIViewController {}

@property (nonatomic, retain) IBOutlet UITableView* tablo;
@property (nonatomic, retain) NSMutableArray* dizi;

@end

Burada biri UITableView türünden bir nesneye, diğeri de dinamik bir diziyi temsil eden sınıf olan NSMutableArray türünden bir nesneye işaret edecek olan iki özellik tanımlamış olduk.  Şimdi "OrnekViewController.m" dosyasını açıp içerisindeki gereksiz metodları sildikten sonra aşağıdaki hale getiriyoruz.

#import "OrnekViewController.h"

@implementation OrnekViewController

@synthesize tablo,dizi;

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.dizi = [NSMutableArray arrayWithObjects:@"elma",@"armut",@"portakal",@"çilek",@"kiraz",@"muz",@"karpuz",@"kavun",nil];
    tablo.rowHeight = 50;
}

- (void)viewDidUnload
{
    self.tablo = nil;
}

- (void)dealloc
{
    [tablo release];
    [dizi release];
    [super dealloc];
}

@end

viewDidLoad metodu, ViewController'ın kontrol ettiği view belleğe yüklendikten hemen sonra çağrılan bir metod. Ekran kullanıcıya gösterilmeden önce yapılması gereken işleri bu metodun içerisinde yapabilirsiniz. Benzer işlerin yapılması için kullanabileceğiniz daha mantıklı metodlar da var ancak burada onları kullanmaya gerek yok. Bu örnekte  yaptığımız işler, tablonun göstereceği verileri içeren dizi değişkenini yaratıp içini meyve isimleriyle doldurmak ve tablonun satırlarının yüksekliğini 50 piksele ayarlamak. viewDidload ve dealloc metodlarında da, önceden yaratmış olduğumuz Outlet ve dizinin hafızadan silinmesini sağladık. Farkettiyseniz meyve isimleri sadece tırnak içerisinde değil, @"" içerisinde yazılı. Bunun sebebi Objective-c içerisinde bu şekilde kullandığınız String'lerin NSString* anlamına geliyor olması. Objective-c ile String'leri hep bu şekilde yazmanız gerekiyor.

Sonraki adım IB'ye dönüp, tanımlamış olduğunuz Outlet'i, arayüze koymuş olduğunuz tabloya bağlamak (File's Owner ikonundan tablomuza kontrol tuşuna basarak sürükle-bırak ile). Bu işlemi de yaptıkyan sonra File's Owner ikonuna tıklayarak ViewController'ınızı seçerseniz, Inspector penceresinin bağlantılar bölmesinin aşağıdaki şekilde olduğunu görmelisiniz. Eğer görmüyorsanız geriye dönüp bir şeyi unutup unutmadığınızı kontrol edin.

Tablo bağlantıları

Şimdilik IB ile işiniz bitti. Tekrar XCode'a dönüp .m dosyası (Bundan sonra .m veya .h dosyası dediğimde "OrnekViewController" adındaki dosyaları kasdettiğimi anlarsınız artık heralde) içerisine tablonun dolmasını sağlayacak şu delegate metodlarını ekleyin.

// Tablonun bize kaç bölmeye sahip olacağını sorduğu metod
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Bölme sayısı bu örnekte 1
    return 1;
}

// Tablonun bize toplam kaç satır göstereceğini sorduğu metod
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Satir sayısı dizinin eleman sayısı kadar olmalı
    return dizi.count;
}

// Tablonun bize hangi satırda hangi hücrenin bulunması gerektiğini sorduğu metod
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Yarrattığımız hücreleri sonradan tekrar kullanabilmemizi sağlayacak olan bir kod belirliyoruz
    static NSString *CellIdentifier = @"Hucre";
    // Bellekte bu koda sahip olan herhangi bir hücre var mı diye bak
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    // Eğer öyle bir hücre yoksa yenisini yarat
    if (cell == nil)
    {
        // Hücre yarat
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        // Satırlar seçilemesin (Bu satırı silerseniz seçtiğiniz satırlar mavi olur)
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }
    // Kaçıncı satır için hücre hazırladığımızı öğren
    int satirNo = [indexPath indexAtPosition:1];
    // Hücre içeriğini hazırla
    cell.textLabel.text = [dizi objectAtIndex:satirNo];
     // Hazırlanan hücreyi döndür
    return cell;
}

Burada eklediğimiz metodların hepsi UITableViewDataSource protokolü içerisinde tanımlanmış. UITableView bileşeni de veri kaynağı olarak bu protokole uyan bir nesneye ihtiyaç duyuyor. Yani nasıl davranması gerektiğini bu metodları çağırıp, metodlardan dönen değerlere bakrak öğreniyor. Tabloda kaç bölme olduğunu numberOfSectionsInTableView: metodundan dönen değere bakarak, her bölmede kaç satır olduğunu tableView:numberofRowsInSection: metodundan dönen değere bakarak, hangi bölmenin hangi sütununda hangi hücrenin olması gerektiğini ise tableView:cellForRowAtIndexPath: metodundan dönen hücreye bakarak öğreniyor (Farkettiyseniz Objective-c de  metod ismi diye bir şey yok. Methodun ismi parametrelerinin isimlerinin birleşiminden oluşuyor. Her parametreden sonra da bir : işareti var). Bu metodların implementasyonunu, tabloya dataSource olarak gösterdiğiniz sınıf içerisinde istediğiniz şekilde yaparak, tablonun dilediğiniz şekilde çalışmasını sağlayabilirsiniz. Yukarıdaki kodda tabloya, tek bölmeli ve dizinin eleman sayısı kadar satıra sahip olmasını söyledik. Hücreleri ise, her satırda dizinin ilgili elemanı gösterilecek şekilde yarattık. Hücrelerin içeriğini ayarlama işlemini, tablolar içerisinde varsayılan olarak bulunan textLabel adlı nesnenin içeriğini değiştirerek yaptık. Eğer programı bu haliyle çalıştırırsanız, aşağıdaki ekilde görüldüğü gibi, kodda vermiş olduğunuz meyveleri listeleyen, etkileşimsiz bir tablo görürsünüz.

Tablonun ilk hali

Eğer kullanıcı tablonun bir satırına bastığında bir şey yapmak, mesela bir mesaj göstermek isterseniz, .m dosyasının içerisine aşağıdaki metodu ekleyebilirsiniz.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    int satirNo = [indexPath indexAtPosition:1];
    NSString* msg =  [NSString stringWithFormat:@"%@ yiyelim",[dizi objectAtIndex:satirNo]];
    UIAlertView* alert = [[UIAlertView alloc] initWithTitle:nil message:msg delegate:nil cancelButtonTitle:@"Tamam" otherButtonTitles:nil];
    [alert show];
    [alert release];
}

Şimdi programı çalıştırıp satırlardan birine tıklarsanız şöyle bir manzara ile karşılaşırsınız.

Tablonun ikinci hali

Satır Silme ve Yer Değiştirme

Satır silme işlemini yapmanın iki farklı yolu var. Biri satır üzerinde parmağınızı soldan sağa götürdüğünüzde silme ikonu çıkarmak, diğeri de tabloyu edit (düzenleme) moduna sokup sildirmek. Öncelikle ilk metodu göstereceğim çünkü zaten ileride yapacağım yer değiştirme işlevselliği için tabloyu edit moduna sokmamız zaten gerekecek.

Satır silme işlemi için tek yapmanız gereken yine UITableViewDataSource protokolünde tanımlanmış olan tableView:commitEditingStyle:forRowAtIndexPath: metodunun aşağıdaki şekilde implementasyonunu yapmak. Açıklamaları kod içerisindeki yorumlarda yaptım.

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{   
    // Bu metod silme işlemi için mi çağırılmış kontrol et
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        // Hangi satırın silindiğini öğren ve satırı diziden de sil
        int satirNo = [indexPath indexAtPosition:1];
        [dizi removeObjectAtIndex:satirNo];
        // Tabloyu güncelle
        [tablo reloadData];
    }
}

Uygulamayı bu haliyle çalıştırıp, parmağınızı herhangi bir satır üzerinde soldan sağa sürüklerseniz, aşağıdaki şekilde bir silme tuşu görecek, tuşa bastığınızda da satırın silindiğini göreceksiniz.

Silme tuşu

Şimdi gelelim tabloyu düzenleme moduna sokmaya. Tablo kendi kafasına göre düzenleme moduna giremeyeceğine göre, ona bunu ne zaman yapacağını söylemenizi sağlayacak bir şeye ihtiyacınız var. Bu ihtiyacı gidermenin en temiz yolu arayüzün alt kısmına yeni bir tuş eklemek. Tuşu ekledikten sonra .h dosyasını bir Outlet ve tuşa basıldığında çağrılacak olan bir metod tanımlayarak aşağıdaki hale getirin.

#import <UIKit/UIKit.h>

@interface OrnekViewController : UIViewController {}

@property (nonatomic, retain) IBOutlet UITableView* tablo;
@property (nonatomic, retain) IBOutlet UIButton* tus;
@property (nonatomic, retain) NSMutableArray* dizi;

-(IBAction)tusaBasildi;

@end

Sonrada tekrar .m dosyasına geçin, @synthesize satırına tuşu da ekleyin ve tuşa basıldığında çağrılacak olan metodun implementasyonunu aşağıdaki şekilde yapın.

-(IBAction)tusaBasildi
{
    if (!tablo.editing)
    {
        tablo.editing = YES;
        [tus setTitle:@"Düzenledim" forState:UIControlStateNormal];
    }
    else
    {
        tablo.editing = NO;
        [tus setTitle:@"Düzenle" forState:UIControlStateNormal];
    }
}

Burada yaptığımız tek şey tuşa basıldığında tabloyu düzenleme moduna sokmak ve tekrar basıldığında normal moduna geri döndürmek. Hangi modda olduğumuzu anlamak açısından tuşun üzerindeki yazıyı da duruma göre değiştirdik. Kodu bu haliyle çalıştırırıp tuşa basarsanız, tablonun aşağıda göstermiş olduğum düzenleme moduna geçtiğini görürsünüz. Bu modun işlevselliğini mutlaka kullandığınız bir uygulamada görmüşüsünüzdür. Soldaki tuşlardan birine basınca sağda silme tuşu çıkıyor ve ona basarak ilgili satırı silebiliyorsunuz.

Tablo Düzenleme Modu

Son olarak .m dosyası içerisinde tablo satırlarının yerlerinin değiştirilmesi için gereken düzenlemeleri yapalım. Bunun için öncelikle hücreleri yarattığınız yere bir satır daha eklemeniz gerekiyor. Nereye ekleyeceğinizi anlamanız için etrafındaki bir kaç satır ile birlikte veriyorum. Yeni eklediğim satırı koyu yaptım.

if (cell == nil)
{
    // Hücre yarat
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    // Satırlar seçilemesin (Bu satırı silerseniz seçtiğiniz satırlar mavi olur)
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    // Satır değiştirme kontrolünü göster
    cell.showsReorderControl = YES;
}
Buna ek olarak, yine UITableViewDataSource protokülünde tanımlanmış olan, satır değişimi ile ilgili metodların implementasyonunu yapmanız gerekiyor. Açıklamlar yine kod içerisinde var. Zaten kodun fazla açıklamaya ihtiyacı olduğunu da düşünmüyorum smiley.

// Tablonun bize satırın hareket ettirilmesine izin verip vermeyeceğimizi sorduğu metod
- (BOOL)tableView:(UITableView *)tableview canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;   
} // Tablonun bize kullanıcının bir satırı bir yerden başka bir yere taşıdığını haber vermek için kullandığı metod
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
    // Satırın eksi ve yeni yerlerini öğren
    int eskiSatirNo = [fromIndexPath indexAtPosition:1];
    int yeniSatirNo = [toIndexPath indexAtPosition:1];
    
    // Satır değişikliğini diziye de yansıt
    id gecici = [dizi objectAtIndex:eskiSatirNo];
    [gecici retain];
    [dizi removeObjectAtIndex:eskiSatirNo];
    [dizi insertObject:gecici atIndex:yeniSatirNo];
}

Bu işlemlerden sonra programı çalıştırısanız, düzenleme modundayken satırları sağ tarafta bulunan ikonlardan tutup sürükleyerek değiştirebilirsiniz.

Örnek tablo projesinin son hali

Yorumlar

Bu yazı hakkında toplam 9 yorum bulunmaktadır. Siz de yorum ekleyebilirsiniz.
Yorum hamdi kara | 23 Eylül 2011 Cuma 01:45:19
benim gibi yeni başlıyanlar için çok yararlı bilgiler var.yazılar iiçin teşekkürler .iyi çalısmalar herkese
Yorum Cihan Tek | 28 Temmuz 2011 Perşembe 00:22:07
Örneğin kaynak kodu ayrı bir dosya olarak yok kodun tamamını buraya yazmışım zaten.
Yorum erhan demirci | 27 Temmuz 2011 Çarşamba 23:44:17
kaynak kod nerede acaba teşkkürler şimdiden
Yorum Cihan Tek | 24 Temmuz 2011 Pazar 14:40:57
Siz tableView:cellForRowAtIndexpath: metodu içerisinde doğru satıra doğru resim verdiğiniz sürece bir şey karışmaz. Ancak satırlar üst üste gelebilir o da satır yüksekliğinin ayarlanmamış olmasından kaynaklanır. Satır yüksekliği de UITableViewDelegate protokolünün tableView:heightForRowAtIndexPath: metodu ile ayarlanabilir.
Yorum salih | 24 Temmuz 2011 Pazar 11:20:28
tableview de her satırda resim göstermek istiyorum. Fakat tableview aşağı yukarı kaydırdıkça resimler karışıyor. Farklı satırlarda çıkıyor. Bunu nasıl yapabilirim. Haber siteleri gibi.
Yorum Selçuk Kızılkaya | 23 Ocak 2011 Pazar 02:16:28
gerçekten çok teşekkürler.
Yorum Cihan Tek | 22 Ocak 2011 Cumartesi 14:15:29
Eğer feedURLConnection adlı özelliği otomatik olarak retain yapacak şekilde tanımlamışsanız, aşağıdaki satırda yaptığınız işlemde bu özelliğin referans sayısı 2 kez artırılır.

self.feedURLConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];

Bir kez objeyi self. diye kullandığınızdan kendi setter metodu çağrılıp retain yapacağı için, bir kez de siz alloc ettiğiniz için.

Sorunu çözmek için ya ordaki self. sözcüğünü kaldırın, ya property'nin retain özelliğini kaldırın, ya da NSURLConnection sınıfının işinize yarar bir convenience constructor'ı varsa alloc etmek yerine onu kullanın. Daha saçma bir çözüm olarak 2 kere release çağırabilirsiniz aşağıda.
Yorum Selçuk Kızılkaya | 22 Ocak 2011 Cumartesi 12:40:27
Merhabalar, bu mesajı iletişim bölümünden göndermeye çalıştım lakin bi problem çıktığı için burdan göndermek istedim. ben sakarya üniversitesi bilgisayar müh. bölümü 3. sınıf öğrencisiyim. 4-5 ay kadardır zaman buldukça iphone uygulamalarıyla uğraşıyorum. memory management olayında sürekli sıkıntı yaşıyorum. mesela her ürettiğim nesneyi kesinlikle relase veya autorealse ediyorum. aksatmıyorum. thread gereken yerlerde NSAutoPoolRelease kullanıyorum ama yinede leaks oluşuyor. yabancı kaynakları da inceledim memory management hakkında sadece oluşturduğunuz objeyi release edin diyor. başka da bi detay yok.

-(id) initWithUrl:(NSURL *) FeedURL
{
self = [super init];
if (self)
{
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:FeedURL];
self.feedURLConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[urlRequest release];
[self.feedURLConnection release];
}
return self;
}

mesela böyle bir init metodum var. init esnasında download edilecek url'i alıyorum. sadece 2 obje oluşturuyorum ve release ediyorum. ona rağmen leak çıkıyor. eğer müsait olduğunuz bir zamanda yardımcı olabilirseniz gerçekten çok sevinirim. şimdiden teşekkürler. iyi çalışmalar.
Yorum Selçuk Kızılkaya | 22 Ocak 2011 Cumartesi 12:33:58
elinize sağlık.

Yorum Yazın

İsim ve soyadınız : E-Posta adresiniz : Güvenlik kodu : Güvenlik Kodu Yorumunuz : Gönder