Monday 15 August 2011

Movie Player'da Pinch Engelleme

Bir durumda movie player içinde pinch (iki parmakla küçültüp büyütme) özelliğini engellemem gerekiyordu. Movie player'ımı tanımlama şeklimden dolayı, kullanıcı tarafından scaling mode değiştiği zaman movie player benim tanımladığım boyuta büyüyordu. Fakat, kullanıcı "pinch" yaptığı zaman, benim dinlediğim "MPMoviePlayerScalingModeDidChangeNotification" olan observer beklediğim gibi davranmıyordu. O nedenle pinch özelliğini engellemem gerekti.

Bunun için, movie player'ın içinde olduğu view controller içinde touchesBegan delege metodunu implement ettim.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
NSArray *array = touch.gestureRecognizers;
for (UIGestureRecognizer *gesture in array) {
if (gesture.enabled && [gesture isMemberOfClass:[UIPinchGestureRecognizer class]]) {
gesture.enabled = NO;
}
}
}
}


Bu metod içinde, UITouch class instance'ı içindeki tüm UIGestureRecognizer objelerinde iterate ediyorum ve ilgili gesture UIPinchGestureRecognizer türünde mi diye kontrol ediyorum. Eğer öyleyse, gesture'u engelliyorum. Benim işimi gördü.

Wednesday 3 August 2011

UIWebView'da Scroll Özelliğini Engelleme

UIWebView UIScrollView'dan türemediğinden dolayı, scrolling ya da bounce parametrelerini kullanma imkanımız yok. Bu nedenle, scroll özelliğini engelleme için iki farklı yöntem aklıma geldi.

İlk yöntemde, UIWebView'ın subView'larından UIScrollView'dan türemiş olan view'u bularak onun parametrelerini NO'ya çektim.

UIView* row = nil;
for(row in webView.subviews){
if([row isKindOfClass:[UIScrollView class] ]){
UIScrollView* scrollRow = (UIScrollView*) row;
scrollRow.scrollEnabled = NO;
scrollRow.bounces = NO;
}
}
İkinci yöntem ise daha çok Delegate üzerine. UIWebView UIScrollView'dan türememiş dedim fakat UISCrollViewDelegate'i implement edebilir. Bu nedenle, eğer bir custom UIWebView yapıp, UIScrollViewDelegate'e delege edersek ve ilgili delege metodlarını da implement edersek scroll özelliğini yakalayabiliyoruz.

@interface CustomWebView : UIWebView …

@implementation CustomWebView

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
scrollView.scrollEnabled = NO;
}
Kullanıcı ekranı çekmeye başladığı an bu metod çağrılıyor olacak. Böylece scroll özelliğini de engellemiş olacağız.

Tuesday 2 August 2011

Universal Uygulamalarda settings.bundle ayrımı

Universal uygulama yazarken, setting bundle'ını iphone ve ipad için ayrıştırmam gerekti. Ipad görünürken iphone'da görünmemesi gereken bir setting parametresi istendi. Bu sorunu şu şekilde çözdüm.

Settings.bundle dosyamızı Other Sources altında tanımladığımızdan bundle dosyamız her iki platform için de tek oluyor mecburen.

Settings.bundle içinde iki tane Root.plist dosyası tanımladım. Bu dosyalardan birine Root~iphone.plist ismini verdim, diğerine de Root~ipad.plist ismini verdim. Her iki dosyanın içindeki değerleri de istediğim şekilde değiştirdim.

Fakat, iOS3.2 versiyonu makine bazlı dosya isimlerini tanımadığından iOS 3.2'de çalışan ipad'lerde Root~ipad.plist dosyası okunamıyordu. Bu nedenle, şöyle bir yönteme gittim. Settings.bundle altında default olarak oluşan Root.plist dosyasını silmedim, sakladım ve bu dosyanın içeriğini Root~ipad.plist dosyasındaki içerikle doldurdum.

Daha pratik bir çözüm bulan varsa paylaşırsa çok sevinirim.

Eski iOS Dönüşü

Xcode'u ve iOS'u yükselttiniz fakat uygulamayı eski iOS ile derlemeniz gerekiyor diyelim. Bu durumla ben, MPMoviePlayerController'da yapılan güncellemelerden dolayı karşılaştım. Bu durumda eski iOS'u tekrar yüklemek istiyorsunuz.

Böyle bir durumda ben ne yaptım, yazayım. iOS 4.2'yi yükledikten sonra eski versiyonum olan iOS 3.2'yi kaybetmiş oldum. O nedenle iOS 3.2'yi tekrar yüklemem gerekti. Öncelikli olarak eski iOS'ların linklerini vereyim:

Netten arakladığım şekilde yazıyorum:

SDK 3.1.3 ve Xcode 3.1.4:
http://developer.apple.com/ios/download.action?path=/iphone/iphone_sdk_3.1.3__final/iphone_sdk_3.1.3_with_xcode_3.1.4__leopard__9m2809a.dmg

SDK 3.2 ve Xcode 3.2.2:
http://developer.apple.com/ios/download.action?path=/iphone/iphone_sdk_3.1.3__final/iphone_sdk_3.1.3_with_xcode_3.2.1__snow_leopard__10m2003a.dmg

SDK 3.2 ve Xcode 3.2.2:
http://developer.apple.com/ios/download.action?path=/iphone/iphone_sdk_3.2__final/xcode_3.2.2_and_iphone_sdk_3.2_final.dmg

SDK 4 ve Xcode 3.2.3:
http://developer.apple.com/ios/download.action?path=/iphone/iphone_sdk_4__final/xcode_3.2.3_and_iphone_sdk_4__final.dmg

SDK 4.0.1 ve Xcode 3.2.3:
http://developer.apple.com/ios/download.action?path=/ios/ios_sdk_4.0.1__final/xcode_3.2.3_and_ios_sdk_4.0.1.dmg

SDK 4.0.2 ve Xcode 3.2.3:
http://developer.apple.com/ios/download.action?path=/ios/ios_sdk_4.0.2__final/xcode_3.2.3_and_ios_sdk_4.0.2.dmg

SDK 4.1 ve Xcode 3.2.4:
http://developer.apple.com/ios/download.action?path=/ios/ios_sdk_4.1__final/xcode_3.2.4_and_ios_sdk_4.1.dmg

SDK 4.2 ve Xcode 3.2.5:
https://developer.apple.com/ios/download.action?path=/ios/ios_sdk_4.2_gm_seed/xcode_3.2.5_and_ios_sdk_4.2_gm_seed.dmg

SDK 4.2 ve Xcode 3.2.5:
http://developer.apple.com/ios/download.action?path=/ios/ios_sdk_4.2__final/xcode_3.2.5_and_ios_sdk_4.2_final.dmg
Buradan indirdiğimiz setup dosyasını çalıştırıyoruz. Install adımlarından introduction, licence, iPhone SDK licence, Destination select adımlarını geçiyoruz. Installation type adımında sağdaki listede en üst maddenin location kolonunda default olarak "Developer" klasörü seçili çoktan seçmeli bir alan gelecektir. Bu alana basarak Other'ı seçiyoruz ve açılan klasör seçme ekranında Developer dizini altına "old" ismiyle bir dizin oluşturuyoruz. "old" dizini seçiliyken Choose diyoruz ve installation'ın bitmesini bekliyoruz. Installation sonrasında ihtiyacımız olan SDK'yı Xcode'un default gördüğü dizine taşıyacağız.

Bunun için şu dizine gidiyoruz:
/Developer/old/Platforms/iPhoneOS.platform/Developer/SDKs/
Bu dizin altında yeni yüklememizle gelen SDK'ları görebiliriz. Benim ihtiyacım olan SDK 3.2 olduğundan iPhoneOS3.2.sdk klasörünü kopyalıyorum.
Default dizin olan şu dizine gidiyorum ve kopyaladığım klasörü yapıştırıyorum:
/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/

XCode'umu restart ettiğimde Project -> Edit Project Settings -> Build altındaki Base SDK seçenekleri içinde iOS 3.2'nin geldiğini görüyorum.

Başka bir çözüm olarak; bunu klasör kopyalamadan direk Base SDK seçeneklerinden Other seçip ilgili dizini buraya girerek yapmayı denediğimde iOS 3.2'yi gösterebildim fakat bu durumda da (missing) hatası verdi. O nedenle kopyalama yoluna gittim.

UIAlertView'da Text Field etc.

uialertview'a textfield ekleme ve programatik olarak uialertview'ı kapatma.

AppStore'dan uygulama indirirken bize username/password girmemiz için açılan bir alertView vardır. Benzer bir alertView yapma ihtiyacım doğduğunda internette biraz araştırma yaptım. Karşıma çıkan bazı çözümlerin private api kullanımından dolayı Apple tarafından reject edildiğini öğrendim. Fakat şöyle bir çözümün doğru çözüm olduğunu düşünerek şu şekilde istediğimi uyguladım.

- (void) alertViewForPass {
passwordInsertAlert = [[UIAlertView alloc] initWithTitle:@"Sifre" message:@"\n\n\n\n\n" delegate:self cancelButtonTitle:@"Kapat" otherButtonTitles:@"Tamam", nil];
passwordInsertAlert.tag = PASS_INSERT_ALERT_TAG;
currentAlertType = PASS_INSERT_ALERT_TAG;

UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(12,40,260,30)];
headerLabel.font = [UIFont systemFontOfSize:13];
headerLabel.textColor = [UIColor whiteColor];
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.shadowColor = [UIColor blackColor];
headerLabel.shadowOffset = CGSizeMake(0,-1);
headerLabel.textAlignment = UITextAlignmentCenter;
[headerLabel setAdjustsFontSizeToFitWidth:YES];
headerLabel.numberOfLines = 2;
headerLabel.text = @"Sifre Ekrani";
[passwordInsertAlert addSubview:headerLabel];
[headerLabel release];

passInsert1Field = [[UITextField alloc] initWithFrame:CGRectMake(16,83,252,25)];
passInsert1Field.font = [UIFont systemFontOfSize:16];
passInsert1Field.backgroundColor = [UIColor whiteColor];
passInsert1Field.secureTextEntry = YES;
passInsert1Field.keyboardAppearance = UIKeyboardAppearanceAlert;
passInsert1Field.keyboardType = UIKeyboardTypeNumberPad;
passInsert1Field.delegate = self;
passInsert1Field.placeholder = @"Sifre giriniz";
passInsert1Field.tag = PIN_INSERT_FIELD_TAG;
[passInsert1Field becomeFirstResponder];
[passwordInsertAlert addSubview:passInsert1Field];

passInsert2Field = [[UITextField alloc] initWithFrame:CGRectMake(16,120,252,25)];
passInsert2Field.font = [UIFont systemFontOfSize:16];
passInsert2Field.backgroundColor = [UIColor whiteColor];
passInsert2Field.secureTextEntry = YES;
passInsert2Field.keyboardAppearance = UIKeyboardAppearanceAlert;
passInsert2Field.keyboardType = UIKeyboardTypeNumberPad;
passInsert2Field.delegate = self;
passInsert2Field.placeholder = @"Tekrar sifre giriniz";
passInsert2Field.tag = PIN_INSERT_FIELD2_TAG;
[passwordInsertAlert addSubview:passInsert2Field];

[passwordInsertAlert setTransform:CGAffineTransformMakeTranslation(0,109)];
[passwordInsertAlert show];
}

Burada UIAlertView'ı (passwordInsertAlert) instance variable olarak header'da tanımladığımızı varsayalım. Aynı şekilde UITextField'ları (passInsert1Field ve passInsert2Field) da instance variable olarak header'da tanımlıyoruz. Tabi, class'ın dealloc metodu içinde bunları release etmeyi de unutmayalım. Bu arada PIN_INSERT_FIELD_TAG gibi değerler benim #define ile tanımladığım değerler. İsterseniz bu değerler yerine direk unique rakamsal değerler de yazabilirsiniz.

TextField için keyboard ve diğer tuşların yakalanması için class'ımızı UITextFieldDelegate'e extend ediyoruz:

@interface [ClassName] : UIView <UITextFieldDelegate>

Bu arada alertView'dan gelecek aksiyonları handle etmek için de aşağıdaki metodumuzu implement ediyoruz:

- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex{
if (alert.tag == PASS_INSERT_ALERT_TAG) {
if (buttonIndex == 1) {
[self hideKeyboard];
[self validateInsert];
}
} else if(alert.tag == PASS_INSERT_ERROR_ALERT_TAG) {
[passwordInsertAlert release];
[self alertViewForPass];
}
}
Burada alertView'lara tanımladığımız tag'lere göre aksiyon alıyoruz. İlk metodumuzda tanımladığımız alertView'ın tag'ı PASS_INSERT_ALERT_TAG olduğunu düşünürsek AlertView'ımızda "Tamam" butonuna basıldığında bu metodda ilk kontrole girecektir. buttonIndex değeri de özellikle değiştirilmediği sürece "Cancel" butonu için 0, "OK" butonu için 1'dir. hideKeyboard metodu extend ettiğimiz UITextFieldDelegate ile gelen ve bizim gerekli durumda extend ettiğimiz bir metod.

UITextFieldDelegate gelen metodları kendime göre kullanmak üzere extend ediyorum.

- (void) showKeyboardAndToolbar {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if(currentAlertType == PASS_INSERT_ALERT_TAG) {
[passInsert1Field becomeFirstResponder];
}
[pool release];
}

- (void)hideKeyboard {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if(currentAlertType == PASS_INSERT_ALERT_TAG) {
[passInsert1Field resignFirstResponder];
[passInsert2Field resignFirstResponder];
}
[pool release];
}

- (BOOL) textFieldShouldReturn:(UITextField *)theTextField {
if(currentAlertType == PASS_INSERT_ALERT_TAG) {
if (theTextField.tag == PIN_INSERT_FIELD_TAG) {
[passInsert2Field becomeFirstResponder];
} else {
[self hideKeyboard];
[self validateInsert];
}
}
return YES;
}

validateInsert meted, alert içindeki textField'lara girilen değerleri valide etmek için implement ettiğim bir metod. Siz de burada istediğiniz bir metodu çağırabilirsiniz.
Yine de textField'ların ve alertView tag'larının nasıl handle edildiğini göstermek adına bu metodu da yazayım.

- (void) validateInsert {
if (![passInsert1Field.text isEqual:passInsert2Field.text]) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Hata"
message:@"Girdiğiniz 2 sifre aynı olmalıdır."
delegate:self
cancelButtonTitle:@"Tamam"
otherButtonTitles:nil];
alertView.tag = PASS_INSERT_ERROR_ALERT_TAG;
[alertView show];
[alertView release];
} else {
//why don't you do something ...
}
}

Burada gördüğümüz gibi hata alert'üne yeni bir tag veriyoruz ki "alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex" metodu içinde alert'ler karışmasın. Eğer bu metod içine gelen alert'ün tag'i PASS_INSERT_ERROR_ALERT_TAG ise tekrar alertViewForPass metodu tetikleniyor.

Çok basit bir çözüm, ama bir o kadar da hayat kurtarıcı. Son olarak da basit ama gerekli bir bilgi daha ekleyelim. Alert'ler üzerindeki "Tamam" butonunu handle edecek metodlar yazdık fakat bir noktada alert'ü de kapatmamız gerekecektir. Bu durumda şu şekilde alert'ümüzü kapatabiliriz:

[passwordInsertAlert dismissWithClickedButtonIndex:0 animated:YES];
Adeta "Cancel" butonuna basılmış gibi alert'ümü dismiss et diyoruz.

iOS 4 ve Local Notification

iOS 4 ile birlikte hayatımıza giren yeniliklerden biri de local notification oldu. Bu sayede server-side'a gerek kalmadan, sertifikaya gerek kalmadan, APN gateway'e bağlanmaya gerek kalmadan client üzerinde notification set edebilecek kod yazabilir hale geldik.

Aşağıda bir local notification set etme örneğini vereceğim. Fakat bunu yazmamdaki asıl amaç basit bir yöntemle local notification'ı silme özelliğini ekleyebilecek bir yol anlatmak.

NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:@"VALUE", @"NOTIFICATION_KEY", nil];

NSString *message = ...;
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = message;
notification.timeZone = [NSTimeZone defaultTimeZone];
notification.fireDate = ...;
notification.userInfo = infoDict;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
[notification release];
Bu şekilde notification'ınımızı set ettik. Burada sonra silmek için kullanacağımız kısım userInfo'ya set ettiğimiz değerler. Geri kalanı standart local notification set etme.

Ne için notification set ediyorsak infoDict içindeki VALUE alanına onun unique bir identifier'ını yazabiliriz. Mesela, ben bunu bir kanal programına hatırlatma için kullandım. Dolayısıyla bu VALUE alanına da program'ın id'sini verdim.

Sileceğimiz zaman ise şöyle yapıyoruz:

UIApplication *app = [UIApplication sharedApplication];
NSArray *notificationArray = [app scheduledLocalNotifications];
UILocalNotification *row = nil;
for (row in notificationArray) {
NSDictionary *userInfo = row.userInfo;
NSString *identifier = [userInfo valueForKey:@"NOTIFICATION_KEY"];
if(identifier != nil){
if([identifier isEqualToString:[NSString stringWithFormat:@"%d", program.programId]]) {
[app cancelLocalNotification:row];
}
}
}

EXC_BAD_ACCESS

iPhone geliştirmesi yapmanın olmazsa olmazı EXC_BAD_ACCESS hatası ile ilgili basit bir debug metodunu yazayım istedim.

EXC_BAD_ACCESS hatasının birkaç nedeni olsa da genelde release edilmiş bir objeyi manipüle etmeye çalışınca bu hatayla karşılaşıyoruz. Debugger konsolda EXC_BAD_ACCESS yazısının ötesinde, hatanın nedenine uygun olarak hatanın verildiği class'ı görebilmek için kullanabileceğimiz bir parametre var.

NSZombieEnabled

Efendim bu parametre neyi sağlar? Release edilen her bir objenin yerine bir dummy obje yani zombi bırakır.

XCode'da Executables altındaki app dosyamızın "Get Info"sunu açarak "Arguments" tab'ına geçiyoruz. Buradaki ekranın alt kısmındaki Variables alanına "+" işaretiyle yeni bir parametre ekliyoruz.

Name: NSZombieEnabled Value: YES

En baştaki checkbox'u da işaretlemeyi unutmuyoruz.

Bu aşamadan sonra build and debug yapıp sonucu debugger konsoldan takip edebiliriz.