[Objective-C] Create Round UIImageView

#import < QuartzCore/QuartzCore.h>

CALayer *imageLayer = myImageView.layer;
CGFloat height = imageLayer.frame.size.height;
CGFloat width = imageLayer.frame.size.width;
CGFloat roundRadius = height > width ? width / 2 : height / 2;

[imageLayer setCornerRadius:roundRadius];
[imageLayer setBorderWidth:1];
[imageLayer setMasksToBounds:YES];
[imageLayer setBorderColor:[[UIColor redColor] CGColor]];



[Objective-C]NSDictionary dictionaryWithObjectsAndKeys: lost content

if we have a custom data object 'Question *obj' and initial a NSDictionary with obj
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                      [NSNumber numberWithInteger:obj.value1], @"QuestionNo",
                      obj.value2, @"QuestionKind",
                      obj.value3, @"Question",
                      nil];

The above 'dict' has 3 key/value pairs in most time.
But, dict may lost the content(less than 3 pairs) if the 'Question *obj' has nil value.
Because [NSDictionary dictionaryWithObjectsAndKeys:] stops to add objects when it finds a nil value.

use Underline in wild card charecter of Like query gives wrong result

●Question
Why the SQL execution always return the wrong result when i use 'underline' ?
select  * 
from  user
where  userid like 'Karen_Chang%'  

●Solution
Use brackets! []
select  * 
from  user
where  userid like 'Karen[_]Chang%'

break label usage in Objective-C and Java

if you have nested for loop or the for loop need to be break in special condition
use 'break label' can satisfy your demand

●Java
myForLoop: {
    for (; ; ;) {
if(someCondition == True)
            break myForLoop;
    }
}

Objective-C
for (;;)
{
    for (;;)
    {
        break; /* breaks inner loop */
    }
    for (;;)
    {
        goto myForLoop; /* breaks outer loop */
    }
}
myForLoop:;

Swipe to delete UITableViewCell

●iOS (2.0 and later)
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [self.myData removeObjectAtIndex:indexPath.row];
        [tableView reloadData];
    }
}

iOS (8.0 and later)
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"delete" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
        [self.myData removeObjectAtIndex:indexPath.row];
        [tableView reloadData];
    deleteAction.backgroundColor = [UIColor redColor];
    return @[deleteAction];
}

But swipe to delete doesn't work at iOS8 
It's Apple's Bug!!!

How to solve?
add the old method 'commitEditingStyle' as below
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
}

Although the Objective-C has the description about the commitEditingStyle as following, iOS 8 is still not working without this method T__T
// After a row has the minus or plus button invoked (based on the UITableViewCellEditingStyle for the cell), the dataSource must commit the change
// Not called for edit actions using UITableViewRowAction - the action's handler will be invoked instead
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;

How to use NSDefault

Primitive Object
// Get
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString* userId = [defaults objectForKey:@"UserId"];
[defaults synchronize];

// Set
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:userId forKey:@"UserId"];
[defaults synchronize];

// Clear
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:@"UserId"];
[defaults synchronize];

Custom Object
// Get
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *formData = [defaults objectForKey:@"CustomObject"];
MyCustomData *data=[NSKeyedUnarchiver unarchiveObjectWithData:data];
[defaults synchronize];

// Set
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:myCustomObject];
[defaults setObject:data forKey:@"CustomObject"];
[defaults synchronize];

// Clear
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:@"CustomObject"];
[defaults synchronize];

UITableViewCell accessoryType is not working

如題,不管Custom Cell或iOS原生的DefaultStyle Cell,都會發生accessoryType不管怎麼設定都無效的狀況,其實無關AutoLayout或XCode版本~
最需要注意的是,編輯狀態是否開啟!

假設某些row設定cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
當UITableView透過 setEditing:YES 開啟編輯狀態,UITableViewCellAccessoryDisclosureIndicator效果會因此不見,只剩下label text,且相對於UITableView的margin等layout呈現都會不同。

如果UITableView很複雜,想同時有編輯狀態又要保持accessoryType ,那麼就要仔細針對每個cell去設定編輯狀態,實際撰寫方式如下

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 1)
        return YES;
    else
        return NO;
}

- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    if(indexPath.section == 1) {
        if(indexPath.row == [self.myData count])
            return UITableViewCellEditingStyleInsert;
        else
            return UITableViewCellEditingStyleDelete;
    } else {
        return UITableViewCellEditingStyleNone;
    }
}

Get/Set a BOOL in the NSDictionary

Objective-C containers can store only Objective-C objects so you need to wrap you BOOL in some object.

Here is the Dictionary
NSMutableDictionary *dictionary1 =[[NSMutableDictionary alloc]init];
[dictionary1  setObject:[NSNumber  numberWithBool:false]  forKey:@"MyBoolValue"];

How to set bool value ?
[dictionary1  setObject:[NSNumber  numberWithBool:false]  forKey:@"MyBoolValue"];

How to get bool value ?
BOOL myValue = [dictionary1  objectForKey:@"MyBoolValue"]  boolValue];

如何改變iOS ActionSheet的字體顏色

承前文如何在iOS的ActionSheet加上圖片,再來記錄一下iOS 7、8改變字體顏色的方法

情境
ActionSheet共有三個Action為Create、Delete、Cancel
Create、Delete字體要是橘色,Cancel則是黑色

1. 舊版UIActionSheet
// ActionSheet程式碼
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil
                                                         delegate:self
                                                cancelButtonTitle:nil
                                           destructiveButtonTitle:nil
                                                otherButtonTitles:nil];
[actionSheet addButtonWithTitle:@"Create"];
[actionSheet addButtonWithTitle:@"Delete"];
[actionSheet addButtonWithTitle:@"Cancel"];
actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
[actionSheet showInView:self.view];

// 變更顏色程式碼
- (void)willPresentActionSheet:(UIActionSheet *)actionSheet {
    // set color for each title
    for (UIView *subview in actionSheet.subviews) {
        if ([subview isKindOfClass:[UIButton class]]) {
            UIButton *button = (UIButton *)subview;
            if([button.titleLabel.text isEqual:@"Cancel")
                [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            else
                [button setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
        }
    }
}

2. 新版UIActionSheet
// ActionSheet程式碼
UIAlertController * actionSheetNew = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* create = [UIAlertAction
                       actionWithTitle:@"Create"
                       style:UIAlertActionStyleDefault
                       handler:^(UIAlertAction * action)
                       {
                           // do something
                       }];
UIAlertAction* delete= [UIAlertAction
                       actionWithTitle:@"Delete"
                       style:UIAlertActionStyleDefault
                       handler:^(UIAlertAction * action)
                       {
                           // do something
                       }];
UIAlertAction* cancel= [UIAlertAction
                       actionWithTitle:@"Cancel"
                       style:UIAlertActionStyleDefault
                       handler:^(UIAlertAction * action)
                       {
                           // do something
                       }];
[actionSheetNew addAction:create];
[actionSheetNew addAction:delete];
[actionSheetNew addAction:cancel];
[self presentViewController:actionSheetNew animated:YES completion:nil];

// 變更顏色程式碼
[create setValue:[UIColor orangeColor] forKey:@"titleTextColor"];
[delete setValue:[UIColor orangeColor] forKey:@"titleTextColor"];
[cancel setValue:[UIColor blackColor] forKey:@"titleTextColor"];

另外,如果想要一次改變所有action的字體顏色,可以針對UIAlertController這樣寫
// 變更顏色程式碼(全部action)
actionSheetNew.view.tintColor = [UIColor orangeColor];

參考資料
如何在iOS的ActionSheet加上圖片

[Objective-C]Copy of Custom class

寫這篇的前因後果Java is 'pass by value'
這篇不談pass by reference、pass by value,或是Objective-C下的深拷貝、淺拷貝,網路上已經不少文章可參考,這邊直接透過最常見的ViewController傳值來演示會遇到的問題與解決辦法。

情境
●有二個ViewController:MainViewController、DetailViewController
●MainViewController有一個mutable的origionalArray,用途是呈現資料於tableview
●DetailViewController也有一個mutable的copyArray,用途是編輯資料於tableview,且資料是複製origionalArray來的
●當DetailViewController編輯結束後,用戶可以選擇"Save / Cancel"決定是否保留這次的編輯資料並回到上一頁MainViewController。若是,則copyArray會被用來更新origionalArray;若否,則不影響origionalArray

問題
●不管用戶選擇"Save / Cancel",origionalArray都會被copyArray影響

解法
●檢查MainViewController傳值的方式
DetailViewController *vc = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];
// 寫法1
vc.copyArray= self.origionalArray;
// 寫法2
vc.copyArray= [self.origionalArray mutableCopy];
// 寫法3
vc.copyArray = [[NSMutableArray alloc] initWithArray:self.origionalArray copyItems:YES];
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:vc];
[self presentViewController:navCon animated:YES completion:nil];

寫法1或寫法2屬於淺拷貝,copyArray都沒有獨立於origionalArray,更新copyArray的同時也會更新origionalArray
這時候嘗試寫法3進行深拷貝,竟會報錯-[ManagementResponser copyWithZone:]: unrecognized selector sent to instance xxxxx 。那是因為origionalArray、copyArray所存放之資料為 'ManagementResponser' 自訂型別,要解決問題必須在custom class實作NSCopying,如何實作請看下圖

實作完成後,寫法3就能正常運行了。

參考資料
Pass-by-Value and Pass-by-Reference in Objective-C: A Refactoring Heuristic
--Copy Array相關
NSMutableCopying
mutable copy copies by reference, not value?
Objective-C: Copy Array, Modify Element (Object) in the Copy ONLY
NSMutableArray of custom classes copy
--NSCopying相關
Copying Objects in Objective-C
iOS OC NSCopying 协议
Objective-c的NSCopying协议

Java is 'pass by value'

昨天被Objective-C的pass by reference、pass by value折騰半天,才發現自己把Java的用法亂套在Objective-C  囧....

紀錄Objective-C之前,先轉錄Java方面正確的說明

[Objective-C] How to open URL ?

NSURL *url = [NSURL URLWithString:@"http://piggy-mylifemystyle.blogspot.com/"];

●使用WebView
    UIWebView *web=[[UIWebView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
    [web loadRequest:requestObj];
    [self.view addSubview:web];

●不使用WebView,直接開啟手機Safari
    [[UIApplication sharedApplication] openURL:url];

iOS Placeholder in UITextView

UITextField擁有placeholder屬性,所以能讓input呈現灰字hint的效果,UITextView卻沒有原生的方式可以直接設定,網路上有套SAMTextView可以參考,本文提供另一種比較簡單的方式。

●h file
@interface QuestionnaireBasicViewController : UIViewController

●m file
實作UITextViewDelegate內的二個method

- (void)textViewDidBeginEditing:(UITextView *)textView {
    if ([textView.text isEqualToString:NSLocalizedString(@"description", nil)]) {
        textView.text = @"";
        textView.textColor = [UIColor blackColor]; //optional
    }
}
- (void)textViewDidEndEditing:(UITextView *)textView {
    if ([textView.text isEqualToString:@""]) {
        textView.text = NSLocalizedString(@"description", nil);
        textView.textColor = [UIColor lightGrayColor]; //optional
    }
}

實際在m file使用
// placeholder for TxtView
myTextView.delegate = self;
myTextView.text = NSLocalizedString(@"description", nil);
myTextView.textColor = [UIColor lightGrayColor]; //optional

參考資料
Placeholder in UITextView

finish iOS UIViewController like Android

相信Android開發者都用過 finish() 將Activity從stack中銷毀,另一方就沒那麼幸運了,Objectice-C可沒有finish()讓開發者一指搞定。

情境描述如下
  A1ViewController
前往A2ViewController
前往A3ViewController
前往B1ViewController
前往B2ViewController,並且銷毀B1ViewController

本來的順序A1→A2→A3→B1→B2
後來的順序A1→A2→A3B2

要達到目標有二種作法
●自訂delegate
    在B1ViewController自訂delegate
    在A3ViewControoler實作delegate

    B1ViewController觸發delegate時呼叫
    [self.navigationController popViewControllerAnimated:NO];

    A3ViewController接到delegate時打開B2ViewController
    [self.navigationController pushViewController:B2ViewController animated:YES];

●UINavigationController的setViewControllers方法
    NSMutableArray * viewControllers = [self.navigationController.viewControllers mutableCopy];
    [viewControllers removeLastObject];
    [viewControllers addObject:B2ViewController];
   
    [self.navigationController setViewControllers:viewControllers animated:YES];


參考資料
equivalent of startactivity & finish in ios?
UINavigationController的setViewControllers方法

check in the Pods into source control ?

在Xcode搭配Git請參考這篇  將Xcode現有專案加入Git

如果專案有使用CocoaPods,.gitignore是否需要針對Pods處理呢?
答案是看你高興XD
只有Podfile 與 Podfile.lock一定要簽入版本控管,其他Pods就依照開發需求決定,簽入與否可參考CocoaPods的官方說明

Benefits of checking in the Pods directory
After cloning the repo, the project can immediately build and run, even without having CocoaPods installed on the machine. There is no need to run pod install, and no Internet connection is necessary.
The Pod artifacts (code/libraries) are always available, even if the source of a Pod (e.g. GitHub) were to go down.
The Pod artifacts are guaranteed to be identical to those in the original installation after cloning the repo.

Benefits of ignoring the Pods directory
The source control repo will be smaller and take up less space.
As long as the sources (e.g. GitHub) for all Pods are available, CocoaPods is generally able to recreate the same installation. (Technically there is no guarantee that running pod install will fetch and recreate identical artifacts when not using a commit SHA in the Podfile. This is especially true when using zip files in the Podfile.)
There won't be any conflicts to deal with when performing source control operations, such as merging branches with different Pod versions.
Whether or not you check in the Pods directory, the Podfile and Podfile.lock should always be kept under version control.

若上述優缺點還是無法協助你決定,可再參考stackoverflow的討論,看看哪種方式適合你~

參考資料
Should I check the Pods directory into source control?
What goes into your .gitignore if you're using CocoaPods?

Encoding and Decoding Objects

無論Android或Objective-C都會有自訂Data Model的應用

把object instance存放於記憶體是最常見的方式,二者的作法如下
Android:制定欄位 & getter/setter
Objective-C:制定欄位

但在下列場景一定要序列化
Android:二個頁面間傳值(例如activityA跳轉至activityB的時候要傳遞一個自訂data)
Objective-C:自訂data儲存於NSDefault

序列化方式
Android:implements Parcelable
Objective-C:implements NSCoding

參考資料
Archiving Objective-C Objects with NSCoding
Encoding and Decoding Objects

How to make "Done" button working on TextField keyboard

STEP 1 Setting 'Return Key' @ Attribute Inspector


STEP 2 Connect the IBAction


STEP 3 implement the IBAction


不要亂寫一堆程式碼污染專案,請用上述的方式完成喔 0.<

Use UIRefreshControl without extending UITableViewController

Apple在iOS 6推出UIRefreshControl,替開發者解決最常用的下拉刷新效果。雖然網路上幾個third-party libraries真的不錯,像SVPullToRefresh幾行程式碼就可以辦到,但可惜久沒更新,畫面呈現實在不合我需求,來試試看原生的UIRefreshControl吧

實際用法很簡單,可以參考這篇iOS Programming 101: Implementing Pull-to-Refresh and Handling Empty Table
不過!網路上的教學都有個前提:使用UIRefreshControl的ViewController都必需繼承UITableViewController,也就是要長這樣
//MyViewController.h file
@interface MyViewController:UITableViewController
{
}
@end

這樣太不實際了,萬一舊專案在後期想要回頭使用UIRefreshControl,或是畫面本身非常複雜,豈不是要額外耗費時間去修改?!解決辦法很簡單,只要透過addSubview把UIRefreshControl加到你的table即可。實際程式碼如下
//MyViewController.h file
@interface MyViewController:UIViewController
{   
    @property(nonatomic, retain) UIRefreshControl *refreshControl;
    @property (weak, nonatomic) IBOutlet UITableView *tbl;
}
@end

//MyViewController.m file
@interface MyViewController () 
@end

@implementation MyViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    
    // get data
    [self getMyData];
    
    // Initialize the refresh control.
    self.refreshControl = [[UIRefreshControl alloc] init];
    self.refreshControl.backgroundColor = [UIColor purpleColor];
    self.refreshControl.tintColor = [UIColor whiteColor];
    self.refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:@"下拉刷新"];
    [self.refreshControl addTarget:self
                            action:@selector(getMyData)
                  forControlEvents:UIControlEventValueChanged];
    [self.tbl addSubview:self.refreshControl];
}

- (void) getMyData {
    // 取資料
}

封裝iOS Framework遭遇各種error之解法

封裝....恩 其實用Xcode 6已經變得很簡單了,不多說~這邊只簡單紀錄一下我遇到的各種狀況

●問題一:image not found
  錯誤資訊
  dyld: Library not loaded: @rpath/authentication.framework/authentication
  Referenced from: /private/var/mobile/Containers/Bundle/Application/A122BB49-F6E0-4C2D-82EE-2933AAC02659/AuthenticationTest.app/AuthenticationTest
  Reason: image not found
 
  解決方式
  把library加入到Embedded Binaries


●問題二:無法載入Framework裡面的資源(例如xib檔)
  錯誤資訊
  Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle

  解決方式
  載入資源時要寫完整的Identifier


●問題三:無法載入Framework內的class
  錯誤資訊
  Symbol(s) not found for architecture armv7

  解決方式
  Framework在build的時候,要針對不同版本建立在Derived Data。也就是說要在模擬器跑就要create一個給模擬器用的Framework;反之,要在真機跑就要另外create一個給真機用的Framework


●問題四:輸出7.0為base的Framework,會有warning
  錯誤資訊
  Embedded dylibs/frameworks only run on iOS 8 or later

  解決方式
  不管他XD  https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1480


●問題五:Class重複存在(通常發生在引用相同的第三方library) 
  錯誤資訊
  Class DDURLParser is implemented in both

  解決方式
  把第三方元件統一封裝給所有專案用,而不是每個專案各自引用

gradle錯誤 造成Android Studio無法安裝apk至device

如題,通常停止gradle即可。但是Android Studio沒有提供GUI協助我們做這件事 @"@

來下command吧

STEP 1 開啟命令視窗
在Windows作業系統請開啟cmd
在MAC作業系統開啟Terminal

STEP 2 前往專案root path

STEP 3 輸入終止指令
在Windows作業系統請輸入
gradlew --stop

在MAC作業系統請輸入
./gradlew --stop

收工...

參考資料

讓iOS TableView可以排序

更改TableView的cell排序,首先要開啟編輯狀態
[self.myTable  setEditing:YES  animated:YES];

此時TableView會長這樣

這不是我們要的效果,我們只要編輯順序,而不是編輯或刪除資料本身。
所以要實作下列二個method

// TableView可以排序
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    // Return NO if you do not want the item to be re-orderable.
    return YES;
}

// 實際排序的方式
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    MyDataItem *dataDict = [self.myDataSource objectAtIndex:fromIndexPath.row];
    [self.myDataSource removeObject:dataDict];
    [self.myDataSource insertObject:dataDict atIndex:toIndexPath.row];
}

作到這邊,的確是可以正確排序,但是~討厭的delete icon還是會出現在cell左側,要解決此狀況請再添加如下代碼

// Editing狀態下的TableCell不要出現紅色delete icon
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
   // The editing style for a row is the kind of button displayed to the left of the cell when in editing mode.
    return UITableViewCellEditingStyleNone;
}

參考資料

iOS 8以上的模擬器多國語系失效

要測試多國語系,無論Apple或Android只要修改模擬器所套用的系統語言即可 。

在iOS 8以後的模擬器卻失效了!怎麼測都是英語@@

解法是透過Xcode修改專案Scheme


PS:除了Scheme,模擬器自身的語系也要改噢~不可只做其中一項
另外附上超強部落客AppCoda的多國語系教學Working with Localization in iOS 8 and Xcode 6

將Xcode現有專案加入Git

在Xcode建立專案通常會問你要不要加入Git


如果你跟我一樣從沒加過,請繼續看下去XD

STEP 1 開啟Terminal終端機 & cd到準備作Git的Xcode Project
其中cd的路徑只要到 "專案名稱.xcodeproj" 這一層即可
若不熟Mac Terminal cd用法,請參考[MAC] 小技巧 – 快速將終端機開啟在指定路徑下
STEP 2 初始化git
輸入git init
強烈建議先跳到STEP 4 & 5處理gitignore,完成後再繼續做STEP 3

STEP 3 加入git
輸入git add .
按下Enter
輸入git commit -m 'initial commit'
讓他跑一下,會出現上圖落落長的資訊
此時,重新開啟Xcode會發現本來反灰無法使用的Source Control可以用了!打開History也有了剛剛第一次Commit的紀錄


STEP 4 處理gitignore
cd回去專案目錄
輸入vim .gitignore

按下Enter

輸入 i 進入編輯模式


STEP 5 取GitHub取得gitignore資訊
前往github/gitignore抓取適合你的gitingore檔案,我的專案是用Objective-C寫的,所以抓gitignore/Objective-C.gitignore就好
接續前一步驟的編輯模式,複製gitignore/Objective-C.gitignore所有內容並貼至Terminal

貼上所有內容

按下Esc鍵
接著輸入:wq 
按下Enter


以上,全部做完就可以成功將現有專案加入git控管
但要注意的是,這只是local git,remote git就看你採用哪一家的,主要是前面的都做成功後,接下來就都用Xcode的Source Control操作就好

參考資料

打造所有iOS版本皆適用的ActionSheet

之前寫過如何在iOS的ActionSheet加上圖片,雖然很簡單~卻要針對新舊版(以iOS 8劃分)寫二種code,且添加上去的圖片位置很醜XD

既然不好用又醜,乾脆手工打造一個可以適用所有iOS版本又漂亮的ActionSheet吧
(8之後已改用UIAlertController,為了方便本文統一稱呼ActionSheet)

如果對專案實作方式沒有興趣,只想使用~請到我的GitHub吧 :)

How To Customize ?
準備要打造的ActionSheet,希望能符合下列需求
1.  選項要可以帶文字&圖片
2.  樣式編排要仿照目前最新版iOS 8,樣式要相同有幾點要注意
     2.1  要有Title
     2.2  除了ActionSheet以外,整個背景都要帶半透明灰底
     2.3  ActionSheet要整個展開,不可出現TableView的scroll形式
3.  行為模式也要仿照,要做到相同效果有幾點要注意
     3.1  ActionSheet要以"滑出"的效果出現
     3.2  若未選中任何選項,而是touch半透明灰底背景,必須以往下收闔的動畫效果關閉ActionSheet
     3.3  ActionSheet被選中的選項要能觸發事件。觸發要透過delegate傳遞到前端,不可寫出高耦合性的view

接下來的實作,會產生三個一組的h、m、xib檔案,這一組以後就是可以重複使用且適用所有版本的ActionSheet


● xib file
觀察前述需求,xib需要灰背景、放選項清單的TableView、能偵測灰背景被按到的gesture

View:選上圖的View,設定Background(需求2.2)


TableView:設定delegate與dataSources(藍框),建立table的連結至h檔的"*actionView"(紅框)


Gesture:將gesture連結至前面設灰背景的View(需求3.2)



● h file
h檔程式碼如下

首先看Line17~25
Line 17:用來當作ActionSheet列表的Table要塞資料,且按下不同item要觸發事件,故此處需實作UITableViewDelegate, UITableViewDataSource
Line 18:method 'setData' 是為了賦予TableView資料,也就是ActionSheet所呈現的選項
Line 19:method 'setTitle' 用來設定ActionSheet標題
Line 21:property '*actionView' 是透過IB(Interface Builder)設定的TableView連結
Line 22:property '*actionItems' 用來存放'setData'傳進來的值
Line 24:property 'delegateOfAction' 是為了委派,簡單來說其他外部class想要透過CustomActionSheet去幫他做事,甚至取得後續資訊,那麼就要將自己指定為 'delegateOfAction' 這個動態型別(如何指定會在之後談到)
ps:如果delegate的概念對你來說太吃力,請參考這篇iOS學習_實作自己的delegate來傳值

再來看Line12~15
Line 14:剛剛提到透過委派(delegate)可以請別人做事,甚至傳遞資訊 。本文的ActionSheet會將被選中item資訊返回,所以定義一個回傳資料id或index的method  'actionSelected:(NSString*) itemIdOrIndex'


● m file
1. 實作View的進場效果,與按下View觸發gesture event後的退場效果(需求3.1 & 3.2)
gesture當然是透過委派

 
viewWillAppear作二件事:註冊gesture,讓畫面進場(參考稍後的 'slideIn' method)

撰寫進場/退場method,特別注意的是退場method 'slideOut' 會用在二個情境,按下灰背景View與選中TableView選項

2. 塞資料
指派TableView資料(Line 74~76)與Title資料(Line 78~80)


3. 為滿足需求2.3,必須動態指定TableView高度,且Title(需求2.1)我打算用TableView Header來實踐(Header高度也必須算進去)。以下一併實現需求2.1 & 2.3
在viewDidLayoutSubViews呼叫method 'adjustHeight' 依照資料筆數計算高度並指定給TableView


計算時依據是否有Title(Line 127)決定高度是否要加上Header高度


4. 實作UITableViewDataSource二個required method


5. 實作TableView項目被選中
TableView被選中(也就是user認為的ActionSheet)除了讓整個View退場,還要透過h檔宣告的 'actionSelected:(NSString*) itemIdOrIndex' method將選中的資訊回傳(Line 65~72)



以上,已成功實作適用所有iOS版本的ActionSheet,接著會用一個空白頁面示範如何透過NavigationBar叫出ActionSheet...

How To Use ?
想要使用CustomActionSheet的Class必須遵循下列事項
1.  宣告實作CustomActionSheetDelegate   (h file)
2.  完成method  'actionSelected:(NSString*) itemIdOrIndex'   (m file)
3.  藉由addSubview將CustomActionSheet.xib附加於畫面上   (m file)

● xib file
本文叫出CustomActionSheet是透過NavigationBar,故畫面上對擺放何種UI Element無特殊需求


● h file
宣告實作CustomActionSheetDelegate



● m file
宣告等一下要使用的property


m檔其他程式碼如下

Line 23:initial
Line 27~30:NavigationBar呼叫method 'showCustomActionsheet'把畫面叫出來
Line 33~41:CustomActionsheet需要的資料與設定都在此完成
Line 83~107:撰寫CustomActionSheetDelegate下的 'actionSelected:(NSString*) itemIdOrIndex' method

Done !
按下NavigationBar,畫面出現帶灰底背景的CustomActionSheet

選擇第三個選項TXT,因為有委派機制~所以可以在主畫面抓到被選中的id為3

參考資料
Implementing a Custom UIActionSheet
Display a custom UIView like a UIActionSheet

Xib not Filling Screen

這陣子練寫iOS,呈現ViewController時都是用push的方式把頁面推出來,今天改用present的方式然出現畫面無法全螢幕的窘境@@
其實跟ViewController或iOS SDK版本都無關!!!
真正的問題如下圖紅框處
解決方式如下圖綠框處,改為Use Asset Caalog即可
此時,再回頭去檢查Images.xcassets,就會看到雖然我們沒有真的去做icon圖片給app,但是已經產生所缺少的空白圖片,上述問題也就解決了
為什麼會這樣呢?引述參考來源的文字如下
The problem is that you don't have a launch image for the 4-inch screen, so the iPhone 5s is treating this app as a 3.5-inch app (iPhone 4) and letterboxing it.

參考資料

如何在iOS的ActionSheet加上圖片

本文紀錄新舊版中,Action要帶圖片的作法
1. 舊版UIActionSheet
2. 新版(iOS 8.0) UIAlertController

1. 舊版UIActionSheet
UIActionSheet *actionSheet = [[UIActionSheet alloc]
    initWithTitle:@"我是8.0以下的Action(UIActionSheet)"
    delegate:self
    cancelButtonTitle:NSLocalizedString(@"取消",nil)
    destructiveButtonTitle:nil
    otherButtonTitles:
                              NSLocalizedString(@"選項一",nil),
                              NSLocalizedString(@"選項二",nil),
                              nil];
     
 // 讓第1個action帶圖片
[[[actionSheet valueForKey:@"_buttons"] objectAtIndex:0] setImage:[UIImage imageNamed:@"yourimagename.png"] forState:UIControlStateNormal];
     
actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
[actionSheet showInView:self.view];


2. 新版(iOS 8.0) UIAlertController
UIAlertController * actionSheetNew = [UIAlertController
    alertControllerWithTitle:@"我是8.0以上的Action(UIAlertAction)"
    message:nil
    preferredStyle:UIAlertControllerStyleActionSheet];

// 定義第1個action
UIAlertAction* action1 = [UIAlertAction
    actionWithTitle:@"選項一"
    style:UIAlertActionStyleDefault
    handler:^(UIAlertAction * action)
    {
     //Do some thing here
     [actionSheetNew dismissViewControllerAnimated:YES completion:nil];
     NSLog(@"選項一..");
    }];
 // 讓第1個action帶圖片
[chat setValue:[UIImage imageNamed:@"yourimagename"] forKey:@"image"];

// 定義第2個action
UIAlertAction* action2 = [UIAlertAction
    actionWithTitle:@"選項二"
    style:UIAlertActionStyleDefault
    handler:^(UIAlertAction * action)
    {
     //Do some thing here
     [actionSheetNew dismissViewControllerAnimated:YES completion:nil];
     NSLog(@"選項二..");
    }];

// 定義第3個action
UIAlertAction* action3 = [UIAlertAction
    actionWithTitle:@"取消"
    style:UIAlertActionStyleDefault
    handler:^(UIAlertAction * action)
    {
     //Do some thing here
     [actionSheetNew dismissViewControllerAnimated:YES completion:nil];
     NSLog(@"取消..");
    }];

[actionSheetNew addAction:action1];
[actionSheetNew addAction:action2];
[actionSheetNew addAction:action3];
[self presentViewController:actionSheetNew animated:YES completion:nil];


參考資料
Adding Images to UIActionSheet buttons as in UIDocumentInteractionController
UIActionSheet is deprecated in iOS 8
除了添加圖片,也可以嘗試修改文字顏色,請參考UIAlertController custom font, size, color

UITableView無資料時該如何呈現

Android的ListView都是用setEmptyView,然後去設定塞進EmptyView的TextView,例如顏色、字體大小等。
且xml檔都要記得放android:id="@+id/empty"的TextView

iOS的做法如下
作法1:使用UITableView自身來呈現
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (self.myDataItems.count == 0) // 資料為空, 只需1個cell呈現"無資料"資訊, 故個數是1
        return 1;
    else
        return self.myDataItems.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (self.myDataItems.count == 0) {
        UITableViewCell *cell = [UITableViewCell new];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
        cell.textLabel.textAlignment = NSTextAlignmentCenter;
        cell.textLabel.textColor = [UIColor lightGrayColor];
        cell.textLabel.text = @"沒有資料";
        return cell;
    }

    // 若資料不為空, 就自行實踐cell的呈現
    static NSString *CellIdentifier = @"MyCustomCellIdentifier";
    MyCustomCell *cell = (MyCustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    // 略...
 
    return cell;
}
作法2:使用UILable客製化呈現
作法1因為用cell呈現,樣式有點難隨心所欲,例如想把"沒有資料"放在畫面正中央,這時候自行new一個UILabel會是比較快的選擇
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      // 不需要額外撰寫 if (self.myDataItems.count == 0)要怎麼處理
     return self.myDataItems.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // 不需要額外撰寫 if (self.myDataItems.count == 0)要怎麼處理, 本method只要專心處理有資料的cell要怎麼呈現即可

    // 若資料不為空, 就自行實踐cell的呈現
    static NSString *CellIdentifier = @"MyCustomCellIdentifier";
    MyCustomCell *cell = (MyCustomCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    // 略...
 
    return cell;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    if([self.myDataItems count] == 0) {
        //create a lable size to fit the Table View
        UILabel *messageLbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, tableView.bounds.size.height)];
        //set the message
        messageLbl.text = @"沒有資料";
        //center the text
        messageLbl.textAlignment = NSTextAlignmentCenter;
        //auto size the text
        [messageLbl sizeToFit];
        
        //set back to label view
        tableView.backgroundView = messageLbl;
        //no separator
        tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        
        return 0;
    }
    return 1;
}
參考資料 UITableView: Display Message In Empty Table View