Nelson 寫些 iOS 開發的東東

本站搬家了

| Comments

為了更高的彈性以及更好的資料自主性,我把部落格搬到 GitHub Page,這邊將不再維護。

想看最新文章的朋友,請移駕到 https://chiahsien.github.io/ ,它也有提供 RSS 訂閱服務喔。

iOS App 如何支援 RTL 語言

| Comments

公司的產品越做越大,前一陣子幫公司的 app 加上阿拉伯文介面,開發過程也累積了一些支援 RTL 語系的心得,藉這個機會跟大家分享。

找到 Native Speaker

對於書寫方向是「從左到右」的我們來說,最困難的其實不是看不懂這個語言,而是不知道這樣的 layout 是否正確,因為 layout 絕對不是全部都無腦的換成「從右到左」就好。所以如果情況許可,最好找個 native speaker 讓團隊諮詢(例如找個當地的員工,或是請當地大學生來打工之類),這樣可以節省不少來回確認界面的時間。

仔細閱讀蘋果文件

很多開發的注意事項都寫在這份蘋果文件裡頭了,開發前跟開發時務必要多次閱讀,會有很大的幫助。另外,不只是 RD 需要閱讀這份文件,PM、QA、Designer 也應該看過,才不會發生 RD 做出正確界面,結果其他人以為是錯的(例如多媒體播放器的控制元件是不需要 RTL 的)。

程式開發的一些小技巧

圖片左右翻轉

如果有特殊需求的話,可以考慮對圖片 localized,這樣就可以提供適當翻轉過的圖片給特定語言,或是你也可以透過程式碼去翻轉圖片。但是假如你需要的就只是左右翻轉的圖片(例如 arrow 或 bullet-list 的圖片),可以很簡單的透過以下方式取得:

UIImage *image = [UIImage imageNamed:@"xxx"];
// 對圖片做一些必要處理
// .....
// 最後再翻轉圖片
image = [image imageFlippedForRightToLeftLayoutDirection];

要注意的是,翻轉一定要放在最後一步,這樣才會得到預期結果。

使用 Auto Layout 及相關技術

正確使用 auto layout 的 leadingtrailing,加上將 textAlignment 設為 NSTextAlignmentNatural,就可以解決九成以上的 RTL 佈局。如果你的程式還沒支援 auto layout,可以趁機逐步轉換過去。

手動計算 Frame

由於種種因素讓你還無法轉成 auto layout(例如為了效能考量,或是有些地方就是用 frame 比較容易,或是轉換成本太大),這時候你就需要判斷現在是否在 RTL 環境來調整 frame。

if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:view.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) {
    // RTL 佈局
} else {
    // LTR 佈局
}
翻轉再翻轉

還有一種情況是不方便用 auto layout,但是手動計算 frame 又有很多額外因素要考量讓你不想計算。舉個常見的例子:有上下兩個可以左右滑動的 scroll view,上面是多個 tab,下面是點選 tab 之後要捲動到特定範圍。常見的做法是上方每個 tab 都有一個 index,下方則是根據選中的 index 計算 contentOffset。在 RTL 並且要手動計算 frame 的情況下,你會發現 index 處理起來很麻煩。

這裡有一個小技巧,我們可以先對最外層的 container view(例如 UIScrollView)左右翻轉,然後再對 subviews 左右翻轉一次。經過兩次翻轉,這些 subviews 就會從右到左排列,而且原有的程式碼幾乎不需要改動。這招或許看起來很 tricky,但它真的很有用,用得好可以節省非常多的時間。

if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:view.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) {
    scrollView.transform = CGAffineTransformMakeScale(-1, 1);
    for (UIView *subview in scrollView.subviews) {
        subview.transform = CGAffineTransformMakeScale(-1, 1);
    }
}

以上就是我們在支援 RTL 時用到的所有方式,希望對大家有幫助。

如何為各個 Pod 指定 Swift 版本

| Comments

最近升上 Swift 4.2,發現我用到的 Pods 有些還沒支援 4.2 導致編譯錯誤。解決方法也很簡單,只要指定每個 Pod target 的 SWIFT_VERSION4.0 即可。

但是我們不能手動在 Xcode 裡頭調整,因為 CocoaPods 會把 Pods 的 SWIFT_VERSION 設為跟你的 project 一樣,所以下次 pod install 又會被改掉。

我們可以在 Podfilepost_install 來自動修改,只要在 Podfile 結尾加入以下片段即可。

post_install do |installer|
  installer.pods_project.targets.each do |target|
      # 我們也可以懶惰不用 if,讓所有 pod 的版本都設為一樣的

      if ['RxSwift', 'RxSwiftExt', 'RxCocoa', 'RxDataSources', 'ProtocolBuffers-Swift'].include? target.name
          target.build_configurations.each do |config|
              config.build_settings['SWIFT_VERSION'] = '4.0'
          end
      end
  end
end

參考來源:https://stackoverflow.com/a/46690240

為何 Git-Flow 可能不適合你

| Comments

什麼是 Git-Flow

Git-FlowVincent Driessen 在 2010 年提出的一套 Git 分支模型,簡單的說,它有 masterdevelop 這兩個主要的分支,以及 feature / release / hotfix 這三個支援型分支,至於各個分支的用途看圖片應該就懂了,或是看原文有更詳細的說明。

由於當時大家對如何使用 Git 還處於摸索的階段,所以當這套規範被提出並且大家發現真的滿好用的之後,它很快就被廣泛的接受。

如何解決 NSTimer 造成的 retain cycle

| Comments

故事是這麼開始的

最近在替公司 app 做健康檢查,找到一些 memory leaks 的問題,其中一個就是由 NSTimer 所引起的 retain cycle。

NSTimer 是個很容易造成 retain cycle 的物件,無論是新手或是老手都很可能一個不留意就踩到這個坑。舉個很常見的例子,這樣寫就產生 retain cycle 了:

@interface MyViewController()
@property (nonatomic, strong) NSTimer *timer;
@end
  
@implementation MyViewController
- (void)viewDidLoad {
  [super viewDidLoad];
  self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
}
- (void)dealloc {
  [_timer invalidate];
  _timer = nil;
}
- (void)timerFired:(NSTimer *timer) {
  // Do something...
}
@end