2012年2月20日 星期一

iOS學習_實作自己的delegate來傳值

在iOS開發過程中,我們會使用很多元件,比如UITableView、UIWebView等等,不管是什麼用途的元件,都有一個共同的特點,就是會有專屬的委任方法(delegate),而知道有哪些與學會怎麼用Apple提供的delegate是第一步。但如果是自己客制化的介面或邏輯,就可能需要自己實作屬於該架構的delegate。

自己寫之前,先想一下如果使用Apple的delegate是什麼情況?
1. 在h檔引用適當的protocol。
2. 依照引用的protocol,於m檔實作必須的方法。
3. 於m檔加入類似:self.delegate=self的程式碼。

所以,我們可以瞭解若是要一個delegate method完成值的傳遞必須具備以下內容:
1. 一個protocol定義檔。
2. 一個delegate method的執行或運算邏輯。
3. 一個引用上述protocol,並實作完成的程式檔。

以下圖來說明:

1. 紫色區塊:新建一個Objective-C protocol檔案,並定義相關方法。檔案命名為olaDelegate,並於內定義method1與method2兩個方法。

#import <Foundation/Foundation.h>
@protocol olaDelegate <NSObject>
@optional
- (void)method1:(NSString*)value1;
- (void)method2:(NSString*)value2;
@end


2. 綠色區塊:於須實作delegate的檔內(可能是任何類型,EX:UIViewController、NSOperation等)定義一動態型別變數,並執行delegate 方法來取得外部傳入的值,以撰寫需要的邏輯。
h檔

#import <Foundation/Foundation.h>
#import "olaDelegate.h"

@interface Operation :NSOperation
{
id<olaDelegate> delegate;
}
@property (nonatomic, assign) id<olaDelegate> delegate;
- (void)someMethod;
@end


m檔

-(void)someMethod
{
[delegate method1:@"ola"];
[delegate method2:@" a pay rise"];
}


3. 藍色區塊:引用protocol,並完成其實作。
h檔

@interface DropDocument_Table : UIViewController<olaDelegate>
{
........
}


m檔

-(void)init
{
Operation *op = [Operation alloc] init];
op.delegate = self;
}
-(void)method1:(NSString*)value1
{
//撰寫取得value1後的反應。
}
-(void)method2:(NSString*)value2
{
//撰寫取得value2後的反應。
}


所以程式跑的流程會是:由某一種方式觸發綠色區塊的SomeMethod後,將value1與value2透過method1與method2傳給藍色區塊,假設藍、綠色區塊都是UIView,那磨就可以完成兩個View的傳值,若是綠色區塊是NSOperation,則可以完成非同步傳輸後callback function的效果。


為什麼這樣可以進行傳值的動作?講到底,原因就是op.delegate = self;,在這程式把整個自己(self)傳給了op.delegate這一個動態型別,再於op內執行寫在self內的方法,來完成兩者交互的傳遞。

這樣的好處在於我們可以撰寫元件(綠色區塊),並透過protocol(紫色區塊)規定別人在使用我們的元件時必須實作哪些方法,而使用元件的人就可以將邏輯寫在自己內部(藍色區塊),並透過我們完成的元件來展現,就像是UITableView的感覺一樣。

2012年2月18日 星期六

iOS學習_判斷離開View(viewWillDisappear)或推進View(viewWillAppear)

若是使用Navigation來作View的轉換,那很有可能會寫出一種應用:第一個View是資料的列表,第二個View是新增或更新資料的介面,當點選某一個資料後,可以到第二個View更動,儲存後返回第一個View,資料列表必須更新,這時就可以利用下述事件來進行處理。

1. viewWillAppear:view將要被推進來。
2. viewDidAppear:view已經被推進來。
3. viewWillDisappear:view將要被推出去。
4. viewDidDisappear:view已經被推出去。

所以,我們可以在第一個View利用viewWillAppear來進行資料更新的相關處理,可能是reloadData或是其他你需要的改變。

假設在第二個View有使用popoverView來提供一些功能,也可以在viewWillDisappear或viewDidDisappear將popoverView關起來。

iOS學習_使用presentModalViewController切換頁面

在iOS當中,若是要切換頁面,除了使用Navigation外,也可以在UIViewController使用presentModalViewController方法,將其他的View推進來。

方法:

MapMain_identifyResult *resultsVC = [[MapMain_identifyResult alloc] initWithNibName:@"MapMain_identifyResult" bundle:nil];
self presentModalViewController:resultsVC animated:YES];
[resultsVC release];

用法很簡單,只要將要推進來的View建立起來,再利用presentModalViewController方法即可。

若要將利用上述方法推進來的View退掉,就可以使用dismissModalViewControllerAnimated方法。

[self dismissModalViewControllerAnimated:YES];


但若是你的程式的架構比較複雜,比如說:在window上有超過一層的View,可能會在推進View的時候造成方向上的錯亂,則可以利用設定modalPresentationStyle為UIModalPresentationCurrentContext來解決。

resultsVC.modalPresentationStyle = UIModalPresentationCurrentContext;


除了UIModalPresentationCurrentContext以外,還有三種PresentationStyle:
1. UIModalPresentationFormSheet:view以視窗的方式出現,很像網頁上js遮照的效果。
2. UIModalPresentationFullScreen:整頁覆蓋。
3. UIModalPresentationPageSheet:直的時候是FullScreen效果,橫的時候是FormSheet效果。

除了控制出現的區塊,也可以控制出現的動畫,利用設定modalTransitionStyle完成。

resultsVC.modalTransitionStyle = UIModalTransitionStyleCoverVertical;


利用這些View的切換,就可以使App的界面更有變化。

2012年2月9日 星期四

iOS學習_xcode4.2利用ad-hoc模式發佈app

開發iOS時,對於僅供內部使用或是尚在測試階段的程式,可能不希望透過App store將程式散播出去,在機器可以掌握的情況下,可以利用ad-hoc的模式來進行發佈。

若以結果論,什麼叫做ad-hoc模式?
1. 要裝軟體的iOS裝置,必須註冊給開發程式的developer帳號。
2. 程式開發者必須針對特定機器,匯出簽證檔。
3. 程式開發者必須以上述簽證檔,匯出程式。
4. 連同簽證檔與程式檔交給安裝者。
5. 安裝者必須將裝置連接iTunes進行安裝。

流程:
1. 註冊iOS裝置:

A. 進到iOS Dev Center,點選右方的iOS Provisioning Portal。

B. 點選左邊Devices後,點擊Add Devices,輸入Device Name與Device ID後點選submit。


2. 建立簽證檔:

A. 點選左邊Provisioning,選第二個tab(Distribution),點擊New Profile按鈕。

B. 選擇Ad Hoc模式,輸入辨識的名稱,選擇App ID,勾選要安裝程式的裝置。

C. 完成後,點擊Actions的download按鈕(若是沒出現,重新整理網頁即可)。

D. 下載後,雙擊剛剛拿到的檔案,將會自動加到Xcode當中。

3. 利用簽證檔,匯出程式安裝檔

A. 打開要匯出的程式專案,為了不要影響原本的設定,到專案info的Configurations, 增加一組for Ad-Hoc的設定。

B. 到專案的Build Settings 搜尋code signing,將剛剛設定的這組選擇剛第二步驟D點建立的profile。

C. 設定完成後,點選set the active scheme,選擇Edit Scheme。

D. 在Archive的Build Configuration選擇第三步驟A點建立的設定。

E. 匯出程式檔,選擇Menu的Product內的Archive,Xcode就會開始編譯。

F. 取得ipa檔,選擇Menu的Windows內的Orgaizer。

G. 選第四個tab(Archives),選擇要產出ipa檔的匯出檔,點擊share。

H. 選擇iOS App Store Package(.ipa),並於Identity選擇對應的簽證。


4. 安裝程式。

A. 將裝置接上有安裝有iTunes的電腦。

B. 將第一步驟得到的簽證檔與第三步驟得到的ipa檔,一起拉入iTunes的資料庫。(若是從來沒利用該方式拉進程式,可能就不會有應用程式的項目,但做過的一次後就自動會產生這個分類)

C. 選擇裝置,點擊應用程式,按下同步,就會開始進行安裝。(若是前述簽證有錯,或是匯出時選擇的簽證錯誤,則會跳出無法安裝的訊息)


總之,經過很多錯誤以後,還是利用ad-hoc模式將程式安裝到機器上了,若是以99元的開發者帳號,可以註冊100台裝置,撇開機器的數量不說,若是一年後到期後,需要改一點點程式也是需要持續的付費才能有更新的權利,這方面真是與Android有著天壤之別。

iOS學習_於UINavigation上增加多個按鈕

許多程式會使用Navigation作為主要架構,但是空空的Navigation上面不免讓人想增加一些東西上去,而iOS SDK也很和善的將Navigation設置了左右按鈕,讓程式開發人員可以藉由簡單的定義加上按鈕。

self.navigationItem.rightBarButtonItem = myBtn;


如果在iPhone上,可能會覺得一個按鈕就已經將位置佔滿了,但在iPad上面卻擁有很大的空間,所以大部份的人應該會希望在上面可以做出更多的變化,或是塞入更多的按鈕。

概念:
1. rightBarButtonItem或是leftBarButtonItem所接收的參數是UIBarButtonItem
2. UIBarButtonItem在init的時候可以利用initWithCustomView接入一個自定義的UIView

以加入一個圖片按鈕,兩個一般按鈕為例:

//定義三個想要加入的按鈕,
btnitem_layercontrol = [[UIBarButtonItem alloc] initWithTitle:@"功能一"
style:UIBarButtonItemStyleBordered
target:self
action:@selector(btn_function1_Click)];
btnitem_findlocation = [[UIBarButtonItem alloc] initWithTitle:@"功能二"
style:UIBarButtonItemStyleBordered
target:self
action:@selector(btn_function2_Click)];

UIImage *image = [UIImage imageNamed:@"search1_30.png"];
UIBarButtonItem *imagetestButton = [[UIBarButtonItem alloc] initWithImage:image
style:UIBarButtonItemStylePlain
target:self
action:@selector(btn_whereIam_Click)];

//將按鈕加到NSMutableArray當中。
NSMutableArray* buttons = [[NSMutableArray alloc] initWithCapacity:3];
[buttons addObject:imagetestButton];
[buttons addObject:btnitem_layercontrol];
[buttons addObject:btnitem_findlocation];

//建立一個UIToolbar來裝載剛剛建立的NSMutableArray
UIToolbar* toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
[toolbar setTintColor:[self.navigationController.navigationBar tintColor]];
[toolbar setAlpha:[self.navigationController.navigationBar alpha]];
[toolbar setItems:buttons animated:NO];
[buttons release];

//將toolbar利用initWithCustomView加入UIBarButtonItem,在指給navigationItem.rightBarButtonItem。
UIBarButtonItem *olaBtn = [[UIBarButtonItem alloc] initWithCustomView:toolbar];
self.navigationItem.rightBarButtonItem = olaBtn;

[olaBtn release];
[toolbar release];


如此,若是你有使用UINavigation,則會看到右邊按鈕變成三個,並分別會呼叫btn_function1_Click,btn_function2_Click與btn_whereIam_Click三個方法。

如果配合UIPopoverController就可以產生很不錯的效果。

2012年2月8日 星期三

動態利用ArcGIS Server Geoprocessing建立等量圖於iOS裝置上呈現_part0

在ArcGIS Server不知道哪一版開始有Geoprocessing相關的功能,但除了在三年前初略碰過,留下有點難用的印象後,就跟ESRI的產品漸行漸遠,最近終於有機會不用偷偷玩這個價格不斐的軟體,所以假日的時候決定這星期再來會會這個能夠表現出ArcGIS Server價格的模組。

所以想要做的架構如下:

1. 假設我有一個很龐大的資料庫,擺放了全台灣各地的某種資料(EX:雨量)
2. 依照ESRI的架構,只要我發佈成服務以後,不管是哪一種api(android、iOS、Flex、SL、JavaScript等)都會有相對應的接收方法(這次以目前在做專案的iOS來實作)
3. 建立一個擁有資料接口與輸出接口的Geoprocessing model,並利用ArcGIS Server發佈成服務形態。
4. 利用iOS將Geoprocessing傳回的資訊於iPad上顯示(如果結果是圖,就將其繪於mapView)。

所以我虛擬已經接到龐大資料庫的資料(x,y,z值),並將該資料送回Server進行Geoprocessing等量圖的處理,再將該結果繪於iPad上。

效果:


實際上的效果,我個人覺得非常非常的好,並且讓ArcGIS Server擁有無限可能的分析功能,很多應用“想”起來都不是夢想。

感謝這兩天被我煩的人,“YES,我接到了,你看看,棒吧!“

iOS學習_建立一個以UINavigationController為基礎的專案

開發行動化裝置程式有一個很普遍的架構是:一個主要頁面可以轉至其他頁面,點選上一頁後可以返回主選單,再繼續別的功能的操作,或是有一個資料列表,點選後會轉到下一個詳細資訊頁面。而在iOS內就叫做Navigation。

若是整個專案要以Navigation來串聯各個UIView,我們就可以在最初始的UIWindow以UINavigationController動態的加入其他UIView。

h檔

#import
#import "MainViewController.h"

@interface AppDelegate : UIResponder {
UINavigationController *navigator;
}

@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,retain) UINavigationController *navigator;

@end


m檔

-(void) initApp
{
//建立一個navigator
self.navigator = [[UINavigationController alloc] init];

self.navigator.navigationBar.barStyle = UIBarStyleDefault;
//加背景圖
//[self.navigator.navigationBar setBackgroundImage:[UIImage imageNamed:@"bg_image"] forBarMetrics:UIBarMetricsDefault];
//改變按鈕顏色
//[self.navigator.navigationBar setTintColor:[UIColor orangeColor]];

//建立mainView
MainViewController *mainView = [[MainViewController alloc] init];
//加標題
mainView.title = @"主選單";
//將mainView放入navigator中
[self.navigator pushViewController:mainView animated:NO];
//將navigator放入window中
[self.window addSubview:self.navigator.view];
}

說明:
1. 可以在navigationBar.barStyle來設定要顯示的形式
2. 若是想用自己的圖片當作Bar的底圖,可以利用setBackgroundImage
3. UINavigationController只是外部的框架,要放入顯示的View可以利用pushViewController。
4. 最後將設定完成的UINavigationController.view以addSubview放入window

爾後要利用按鈕或是點選TableView以後轉到下一頁,都可以利用self.navigationController pushViewController的語法放入

sv_DMain .title = @"功能一";
[self.navigationController pushViewController:sv_DMain animated:YES];


當你使用這句語法,iOS SDK則會去判斷,目前使用的View是否有UINavigationController,若有則會自動加上應該有的內容。

而最基本的內容就是:Title與返回鍵。

可以得到很不錯的效果:

轉頁後:

2012年2月6日 星期一

iOS學習_Error-CodeSign error: code signing is required for product type 'Application' in SDK 'iOS 5.0'

如果已經申請開發者帳號,但是發佈到實機測試時卻碰到CodeSign error: code signing is required for product type 'Application' in SDK 'iOS 5.0'這一個錯誤,那有可能是在Build Settings上的Code Signing的設定不符合。


將Any iOS SDK改為iPhone Developer即可。

2012年2月3日 星期五

iOS學習_ESRI iOS SDK_圖層開關功能(Layer switch)

結合MapView上的Layer操作UITableView於Cell加上UISwitch我們就可以完成一個ESRI“應該沒有附”但又非常基本的功能:圖層開關。

目前可以擺上列表,並於開關UISwitch後,地圖作出相對應的改變。自覺效果不錯,特來慶祝一下,ㄎ。

效果:


我想這個功能應該價值今天晚上吃鹹酥雞但是沒有罪惡感!!!!?

iOS學習_於UITableView加上UISwitch(判斷點到哪一個)

某些情況下,我們會製作許多Check box的介面,來提供使用者做多項目的勾選(例如圖層是否顯示的勾選),因為屬於項目不定的類型,所以通常會想用列表的方式展現,而在iOS當中Check Box就是UISwitch,而可參展的列表就是UITableView。

方法:
1. 於tableView Delegate需實作的方法cellForRowAtIndexPath中,指定cell.accessoryView的屬性

UISwitch *switchview = [[UISwitch alloc] initWithFrame:CGRectZero];
switchview.on = YES;
[switchview addTarget:self action:@selector(chick_Switch:) forControlEvents:UIControlEventValueChanged];
cell.textLabel.text = layername;
cell.accessoryView = switchview;
[switchview release];

說明:accessoryView屬性將會把指定的View加到TableView Cell的右側。

2. 撰寫上述的chick_Switch方法,該方法將會在改變UISwitch值時觸發(也就是開變關、關變開)
h檔

-(IBAction) chick_Switch:(id) sender;

m檔

-(void)chick_Switch:(id)sender
{
UISwitch *switchView = (UISwitch *)sender;
if ([switchView isOn]) {
NSLog(@"open");
} else {
NSLog(@"close");
}
}


如此,就可以將UISwitch加到TableView的每一個Cell當中,並且在開關的時候觸發chick_Switch事件,來作相關的處理。

雖然可以判斷點到的是開還是關,但卻沒辦法判斷點到哪一個?我們可以利用superview的方法,來往上層尋找可以判斷的內容,比如Cell的文字,或是indexPath。

方法:

-(void)chick_Switch:(id)sender
{
UISwitch *switchView = (UISwitch *)sender;
UITableViewCell *cell = (UITableViewCell *)switchView.superview;
NSString *layerName = cell.textLabel.text;

UITableView *tableView = (UITableView *)cell.superview;
NSIndexPath *indexPath = [tableView indexPathForCell:cell];

if ([switchView isOn]) {
NSLog(@"%@ open",layerName);
NSLog(@"%@ open",indexPath);
} else {
NSLog(@"%@ open",layerName);
NSLog(@"%@ open",indexPath);
}
}


效果:

iOS學習_ESRI iOS SDK_AGSMapView MapLayer的控制

一般利用ESRI SDK開發相關的地圖應用程式,最常使用的三種圖層類別,莫過於TiledMapService、DynamicMapService與Graphics,而對應到iOS當中的定義分別為:

*AGSTiledMapServiceLayer
*AGSDynamicMapServiceLayer
*AGSGraphicsLayer

加入地圖的方法:
AGSTiledMapServiceLayer

AGSTiledMapServiceLayer *tiledLayer = [[AGSTiledMapServiceLayer alloc] initWithURL:[NSURL URLWithString:kTiledMapServiceURL]];
[self.mapView addMapLayer:tiledLayer withName:@"ola Tile"];


AGSDynamicMapServiceLayer

self.dynamicLayer = [[[AGSDynamicMapServiceLayer alloc] initWithURL:[NSURL URLWithString:kDynamicMapServiceURL]];
NSArray *layerArray = [NSArray arrayWithObjects:[NSNumber numberWithInt:26],nil];
self.dynamicLayer.visibleLayers = layerArray;
[self.mapView addMapLayer:self.dynamicLayer withName:@"ola Dynamic"];


AGSGraphicsLayer

self.olagraphic = [AGSGraphicsLayer graphicsLayer];
[self.mapView addMapLayer:self.olagraphic withName:@"olagraphicsLayer"];


總之,不管是要加入那一種圖層,都是使用addMapLayer的方法,而在使用該方法時,都回返回一個UIView,如果想要一開始就設定透明度與顯示與否,就可以在接下來後設定alpha或是hidden的屬性。

UIView *dynamicLayerView = [self.mapView addMapLayer:self.dynamicLayer withName:@"Dynamic Layer"];
dynamicLayerView.alpha = 0.5;
dynamicLayerView.hidden = NO;


有時候我們可以會想要知道mapView到底目前有哪些圖層,就可以藉由mapLayers方法取得圖層列表,利用mapLayerViews方法取得圖層視圖。

NSArray *layerLists = [self.mapView mapLayers];
AGSDynamicMapServiceLayer *layerGroup = [layerLists objectAtIndex:i];

NSDictionary *layerLists = [self.mapView mapLayerViews];
dynamicLayerView = [layerLists objectForKey:@"ola Dynamic"];


取得對應的dynamicLayerView後,就可以動態的改變是否顯示與透明度。

iOS學習_解決TabBar於iPad上顯示不完全的問題

上一篇我們可以利用程式碼,以設定UITabBarController的viewControllers方式,將我們希望的View以Tab的方式加到畫面上,但是在iPad上的結果並不盡如人意,因為有一半的TabBarItem被遮住了。


根本原因我並不是非常清楚,但是如果觀察UITabBarController的高度(view.frame.size.height)會發現,該高度在iPad上面始終少了20,所以推斷在某種UI配置的情況下,會讓iOS SDK在自動計算UITabBarController應有的高度時,產生20的偏移。

解決方式:所以我們要做的就是把那個20還給他。

tabBarController.view.frame = CGRectMake(0, 0,應有的寬, 應有的高);

*如果你的界面比較單純,那摸可以直接指定定值給他。
*但是如果放UITabBarController的View是一個隨螢幕方向或是任何條件會改變高度的框架,那麼你必須將該程式碼寫在一個可以動態抓到目前框架高度的位置,比如說我放的View是一個UIPopoverController,TabBar的高度就會因為螢幕旋轉而不同,或是因為鍵盤推上時而改變。

效果:


iOS學習_於UIViewController加上UITabBarController

在系統開發時,很多時候會有一些相類似的功能項目,需要在有限的界面上呈現,這時候通常都會以分頁(tab)的方式來呈現,在ipad當然也是,所以我們會看到很多程式都是以UITabBar來作為UI設計的主軸,若是遇到項目數目不定時,會希望以程式碼動態控制的方式來完成。

一般來說,我們會碰到的狀況是要在UIViewController加上TabBar的效果,但因為我們要加上的是包含UITabBarItem的UITabBar,所以必須操作最外層的UITabBarController。

h檔

#import
#import "FindLocation_hole.h"
#import "FindLocation_pipe.h"
#import "FindLocation_town.h"

@interface MapMain_FindLocation : UIViewController
{
UITabBarController *tabBarController;
FindLocation_hole *findhole;
FindLocation_pipe *findpipe;
FindLocation_town *findtown;
}
@property (nonatomic, retain) UITabBarController *tabBarController;
@property (nonatomic, retain) FindLocation_hole *findhole;
@property (nonatomic, retain) FindLocation_pipe *findpipe;
@property (nonatomic, retain) FindLocation_town *findtown;

@end

說明:我預計要加上三個tab,所以包含要加上的tab的View都一併宣告。

m檔

#import "MapMain_FindLocation.h"

@implementation MapMain_FindLocation

@synthesize findhole,findpipe,findtown,tabBarController;

-(void) initView
{
tabBarController = [[UITabBarController alloc] init];

findhole = [[FindLocation_hole alloc] init];
[findhole.tabBarItem initWithTitle:@"功能三" image:[UIImage imageNamed:@"search1_30" ] tag:0];

findpipe = [[FindLocation_pipe alloc] init];
//[findpipe.tabBarItem initWithTitle:@"功能二" image:[UIImage imageNamed:@"search2_30" ] tag:1];
[findpipe.tabBarItem initWithTabBarSystemItem:UITabBarSystemItemFeatured tag:1];

findtown = [[FindLocation_town alloc] init];
[findtown.tabBarItem initWithTitle:@"功能三" image:[UIImage imageNamed:@"search2_30" ] tag:2];

tabBarController.viewControllers = [NSArray arrayWithObjects:findhole,findpipe,findtown, nil];
[self.view addSubview:tabBarController.view];
}

說明:
在加入UITabBarItem時有兩種方式,一個是利用系統內的參數來加,那麼圖與說明都會跟SDK內的設定相同,或是你可以自己定義說明與加上去的文字。

最重要的是最後兩句,將各個要放入的View與UITabBar都定義好後,要放入UITabBarController的viewControllers,並且將UITabBarController加到目前的UIViewController內。

效果:


*如果你使用iPhone來看效果,那就會非常的正常,但是如果使用iPad就會看到上面的結果,TabBarItem的部分被切掉一半,並沒有完全地顯示。(解決方式