2016年6月22日 星期三

筆記一下rxswift遇到disposeBag的scope問題

//userRestoreObservable被訂閱時,會去server抓東西
//原本是在DataUpdate這個struct中寫disposeBag,setupInitialInfo完會釋放裡面的observable。
//但當userRestoreObservable的next closure做完,裡面的物件都被釋放掉,
於是setupInitialInfo的disposeBag也被釋放,就接不到server回傳時的callback。
//解決方法是把disposeBag改成外部注入,讓它被宣告在closure外部,這樣才可以保證裡面的closure不會先被釋放掉。
func fetchUserByRestoreInfo(restoreInfo: RestoreInfo, completion: (()->Void)?) {
        let disposeBag = DisposeBag()
        userRestoreObservable(restoreInfo).subscribeNext { (newUserObj) in
            prettyLog(newUserObj.debugDescription)
                var userDefault = self.userDefault
                userDefault.userPackage = newUserObj
                userDefault.isNewLounch = false
                DataUpdate().setupInitialInfo(disposeBag)
                completion?()
            }.addDisposableTo(disposeBag)

    }

2016年6月16日 星期四

present出二個modelView之後要dismiss回去rootViewController (iOS5.0之後適用)

if let presentingVC = self?.presentingViewController {
    self?.dismissViewControllerAnimated(false, completion: {
        presentingVC.dismissViewControllerAnimated(false, completion: nil)
    })

}

//這裡的重點是presentingVC是把當下VC給present出來的VC,你也可以看作parentViewController。
而你當前VC所present出來的VC,會叫presentedVC,也可以看作是childrenViewController.

presentingVC ---(present)---> yourViewController ---(present)---> presentedVC

2016年6月3日 星期五

記錄一個lazy的應用場景

放在首頁的一個tableview要用到的datasource必須跟server查詢才能取得,
但跟server查詢之前要先等到launch時的資料已經下載完。

因為我是用RxSwift所以我要用Observable做為datasource竹
這時候你在init的時候就可以把datasource的資料來源先設為lazy的參數,
lazy var items: Observable<[SectionModel<String, DepositList.Option>]>? = self.getItems()
它就會先init好,但等到你真正要的時候才去subscribe。

2016年5月31日 星期二

讓tableCell的分隔線從頭連到尾(iOS 8以後)

//viewDidLoad
tableView.layoutMargins = UIEdgeInsetsZero
tableView.separatorInset = UIEdgeInsetsZero

//setup Cell
cell!.layoutMargins = UIEdgeInsetsZero

2016年5月25日 星期三

UIScrollView裡面的點擊事件反應很慢

var delaysContentTouchesBool

If the value of this property is true, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false , the scroll view immediately callstouchesShouldBegin:withEvent:inContentView:. The default value is true.

同樣一個UISegmentedControl,加在ScrollView裡面反應就異常的慢,原來是UIScrollView的subview上的touch事件會被延遲,目的是為了確定當次的手勢不是要滑動。
如果你想讓你的scrollView以點擊事件為主:
yourScrollView.delaysContentTouches = false
如果你想讓你的scrollView以滑動事件為主:
yourScrollView.delaysContentTouches = true

2016年5月20日 星期五

讓UIButton的文字title顯示在圖片上

// 因為UIButton預計是將title接在image的右邊,所以要用UIEdgeInsets把title往左移到跟圖片相等的位置。
if let imageWidth = yourButton.imageView?.intrinsicContentSize().width {
        yourButton.titleEdgeInsets = UIEdgeInsetsMake(0, -imageWidth, 0, 0)

        }

// 其實setImage的作用應該是在Button上的加上小圖示
[[小圖示]按鈕文字]

// 另外一種作法是用setBackgroundImage來作為Button的底圖
button.setBackgroundImage(UIImage(named: "home_button.png"), forState: .Normal)

button.setBackgroundImage(UIImage(named: "home_button_light.png"), forState: .Highlighted)

2016年4月28日 星期四

為你的iOS專案加入新字型


  1. 把字型檔拉進你的project中(只能用.ttf或.otf的檔案)
  2. 去info.plist加入Fonts provided by application,把新增的檔名加入item0的string(若有下一個檔案就加到item1的string)。
  3. print("\(UIFont.familyNames())")看看你新增的字型有沒有被加進去。
  4. 最後去print("\(UIFont.fontNamesForFamilyName("Noto Sans CJK TC"))")看看你加入的family裡面有那些字型你可以用。self.titleLabel.font = UIFont(name: "NotoSansCJKtc-Bold", size: 20)

p.s. 可以用appearance把這些font都變成預設值。
struct Theme {
    static func setDefaultFont() {
        UILabel.appearance().font = UIFont(name: "NotoSansCJKtc-Regular", size: 17)
        UITextField.appearance().font = UIFont(name: "NotoSansCJKtc-Regular", size: 17)
        UITextView.appearance().font = UIFont(name: "NotoSansCJKtc-Regular", size: 17)
    }
}

2016年4月26日 星期二

自動調整ScrollView讓textField適應鍵盤高度

直接把我的code貼上來,重點是不要改變scrollView.contentSize,因為auto layout時會自動設定。
----------------------------------------------------------------
//
//  BaseScrollViewController.swift
//  BaseSettings
//
//  Created by HarveyHu on 4/26/16.
//  Copyright © 2016 HarveyHu. All rights reserved.
//

import UIKit
import SnapKit
import RxSwift

class BaseScrollViewController: UIViewController {
    let contentView = UIView()
    let scrollView = UIScrollView()

    var originalContentInsets = UIEdgeInsetsZero
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setUI()
    }
    override func viewDidAppear(animated: Bool) {
        super.viewWillAppear(animated)
        originalContentInsets = scrollView.contentInset
    }
    
    override func setUI() {
        super.setUI()
        self.scrollView.addSubview(contentView)
        self.view.addSubview(scrollView)
        
    }
    
    override func setUIConstraints() {
        super.setUIConstraints()
        
        scrollView.snp_makeConstraints { (make) in
            make.top.equalTo(self.view)
            make.bottom.equalTo(self.view)
            make.leading.equalTo(self.view)
            make.trailing.equalTo(self.view)
        }


        contentView.snp_updateConstraints { (make) in
            make.top.equalTo(self.scrollView)
            make.leading.equalTo(self.scrollView)
            make.trailing.equalTo(self.scrollView)
            make.bottom.equalTo(self.scrollView)

        }
    }
    
    // MARK: - Keyboard
    @objc override func keyboardWasShown(aNotification: NSNotification)
    {
        guard let info = aNotification.userInfo, kbSize = info[UIKeyboardFrameBeginUserInfoKey]?.CGRectValue().size else {
            return
        }
        
        let contentInsets = UIEdgeInsetsMake(originalContentInsets.top, originalContentInsets.left, kbSize.height, originalContentInsets.right)
        self.scrollView.contentInset = contentInsets
        self.scrollView.scrollIndicatorInsets = contentInsets
    }
    
    @objc override func keyboardWillBeHidden(aNotification: NSNotification)
    {
        let contentInsets = originalContentInsets
        self.scrollView.contentInset = contentInsets
        self.scrollView.scrollIndicatorInsets = contentInsets
    }

}
------------------------------------------------------------------
要用的時候就繼承這個VC,把所有內容放到contentView上,但在contentView的constraints設定上要注意寛度可以固定,但高度不要寫死,要跟著內容高度而定。
contentView.snp_updateConstraints { (make) in
            make.width.equalTo(self.view)
            make.bottom.equalTo(self.theMostBottomView).offset(10)

        }

2016年4月21日 星期四

切換xcode預設路徑到不同位置

OSX有提供一個方便的指令
xcode-select -p 印出目前設定的路徑
xcode-select -s /yourXcodePath/Developer 設定路徑
xcode-select -r 重設回預設的路徑(/Applications/Xcode.app/Contents/Developer)

2016年3月22日 星期二

一個好用的iOS BLE開源第三方framework

https://github.com/HarveyHu/BLEHelper

BLEHelper

一個可以優雅支援一對多BLE裝置(Bluetooth Low Energy)操作的開源框架。(swift 2.2)

安裝(Installation)

CocoaPods

如下設定你的Podfile:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'BLEHelper', '~> 1.0'
設定好後就執行這個指令:
$ pod install

Carthage

使用Carthage安裝,把下面這行加入你的Cartfile中:
github "HarveyHu/BLEHelper" ~> 1.0.0
設定好後就執行這個指令:
$ carthage update --platform iOS
Xcode的設定請參考Carthage的官方網頁。

用法(Usage)

先在你的檔案導入BLEHelper框架:
import BLEHelper
然後把它作為一個已經初始化的property:
let bleHelper = BLECentralHelper()

掃瞄(Scan)

掃瞄附近的BLE裝置:
bleHelper.scan(1.0, serviceUUID: nil) { (devices) -> (Void) in
            //TODO: show your devices
        }

Connect

用已存在的device物件來連線:
bleHelper.connect(yourPeripheral) { (peripheral, error) -> (Void) in
            //TODO: do something when connected
        }
用 deviceUUID (peripheral.identifier) 的陣列 來一次連線多顆裝置:
self.bleHelper.retrieve(deviceUUIDs: [deviceUUIDString], completion: {(peripheral, error) -> (Void) in
            if error != nil {
                prettyLog("error: \(error?.description)")
                completion?(success: false)
                return
            }
            prettyLog("connect with \(peripheral)")
        })

操作(Operation)

讀取(read):
bleHelper.readValue("yourDeviceUUID", serviceUUID: "yourServiceUUID", characteristicUUID: "youCharacteristicUUID") { (success) -> (Void) in
            prettyLog("is read success: \(success)")
    }
設定是否開啟通知(enable notification):
bleHelper.enableNotification(true, deviceUUID: "yourDeviceUUID", serviceUUID: "yourServiceUUID", characteristicUUID: "youCharacteristicUUID") { (success) -> (Void) in
        prettyLog("set notify success: \(success)")
    }
寫入(write):
let command = "yourCommand"
if let data = command.dataUsingEncoding(NSUTF8StringEncoding) {
        bleHelper.writeValue(data, deviceUUID: "yourDeviceUUID", serviceUUID: "yourServiceUUID", characteristicUUID: "youCharacteristicUUID", withResponse: true) { (success) -> (Void) in
            prettyLog("is write success: \(success)")
        }
    }

委派(Delegate)

首先要先在你的Class開頭宣告符合BLECentralHelperDelegate。
這個委派包含兩個Callback方法:
一個是在裝置斷線時callback:
func bleDidDisconnectFromPeripheral(peripheral: CBPeripheral) {
    //TODO: do something...
}
另一個是用來接收裝置所回傳的資料:
func bleCentralDidReceiveData(data: NSData?, peripheral: CBPeripheral, characteristic: CBCharacteristic) {
    //TODO: do something...
}

範例(Example)

用Xcode打開 BLEHelper.xcworkspace , 把 Scheme 設為 "BLEHelperExample." 後就可以在你的iPhone或iPad上面執行啦!

License

MIT License

2016年3月10日 星期四

修改預設物件中所包含的物件的外觀

//取得它的appearance代理物件,可以修改它允許的外觀
例如:
想要修改UIPageViewController中的pageControll的背景色
但它沒有開給你pageControl物件的接口,就可以用以下的方式取得
let pageControlAppearance = UIPageControl.appearanceWhenContainedInInstancesOfClasses([TutorialViewController.self])
pageControl.backgroundColor = UIColor.redColor()

2016年3月1日 星期二

Choosing a Developer Membership

Developer Program:
  1. Individuals(個人)
      • 可上架App Store
      • 提供100組uuid數作實機測試
      • 只可個人使用(單一帳號)
      • 99 USD
  2. Organizations(組織):要提供公司的D-U-N-S Number(鄧白氏編碼)才可申請
    1. Companys(公司)
      • 可上架App Store
      • 提供100組uuid數作實機測試
      • 可多人一起使用(多帳號)
      • 99 USD
    2. Enterprises(企業)
      • 不可上架App Store
      • 提供不限制實機安裝數目
      • 可多人一起使用(多帳號)
      • 299 USD

2016年2月19日 星期五

如何在自己的專案中加入其他專案

有時候你想要引用一個第三方專案,但卻發現它沒有整理到Carthage或Cocoapods中,
這時候你要怎麼辦呢?

其實只有一句話:「將它的xcodeproj檔拉到你的專案的navigator之中,再把相關的dependency設定好」就行了。

設定的方式也很簡單,(當下使用版本為Xcode 7.2.1)

  1. 到專案檔的Build Phrases頁籤下的Target Dependencies中按"+"加入你要用的第三方專案的target。
  2. 到專案檔的General頁籤下的Embedded Binaries中,加你的第三方framework,這時候下面的Linked Frameworks and Libraries 應該會順便自動加入,如果沒有的話,你再將它手動加入一次就好。
簡單二個步驟,Build Success!



2016年2月18日 星期四

Carthage好用到回不去了

Carthage跟Cocoapods不同,它是單純的幫你把framework準備好,要用的你再自己拉進去你的project之中,再也不用看到額外的workspace檔案了!

跟Cocoapods 一樣,Carthage也需要一個Cartfile來告訴它你想生成那些framework。
不過因為Carthage目前只支援git,所以格式也相對簡單
github "ReactiveCocoa/ReactiveCocoa"
這樣就可以抓到最近版的framework,但你也可以指定你要的版本或是branch:
# Require version 2.3.1 or later
github "ReactiveCocoa/ReactiveCocoa" >= 2.3.1

# Require version 1.x
github "Mantle/Mantle" ~> 1.0    # (1.0 or later, but less than 2.0)

# Require exactly version 0.4.1
github "jspahrsummers/libextobjc" == 0.4.1

# Use the latest version
github "jspahrsummers/xcconfigs"

# Use the branch
github "jspahrsummers/xcconfigs" "branch"

# Use a project from GitHub Enterprise
github "https://enterprise.local/ghe/desktop/git-error-translations"

# Use a project from any arbitrary server, on the "development" branch
git "https://enterprise.local/desktop/git-error-translations2.git" "development"

# Use a local project
git "file:///directory/to/project" "branch"
等你準備好Cartfile之後,用terminal在你的專案資料夾鍵入
carthage update --platform iOS
它就會幫你把framework建好放在./Carthage/Build的資料夾中(framework的source code會在./Carthage/Checkouts中)。
你將Build資料夾中的framework拉進你的Xcode的Linked Frameworks and Libraries之中就可以用了。

最後,雖然這樣就可以使用,但如果你想要Run在Simulator時會發現出現如下的錯誤
Command /bin/sh failed with exit code 1

所以如果你想要用Simulator做測試的話,要多做下列的步驟:
  1. 到你的application的target頁面選Build Phrases頁籤。
  2. 按下"+"選"New Run Script Phase"新增一個Run Script。
  3. 在shell下的黑色區域,輸入下列指令/usr/local/bin/carthage copy-frameworks
  4. 最後在input files把你的frameworks的部份加進去
$(SRCROOT)/Carthage/Build/iOS/YourFramework1.framework
$(SRCROOT)/Carthage/Build/iOS/YourFramework2.framework

大功告成,現在你可以看到Simulator彈出來嘍!

P.S.你也可以在update時使用下列參數
carthage update --platform iOS --use-submodules --no-use-binaries

第一個參數說明你只要建立iOS平台的資料夾。
第二個參數讓這個專案以git submodules的型態加入。
第三個參數確保你的framework是在本機Build出來的。

P.S.你也可以用bootstrap指令來抓frameworks
bootstrap和update的差別在於:
update是從你寫的Cartfile中抓取你想到的framework之後,才建立Cartfile.resolved檔案。
而bootstrap是不管你的Cartfile,直接去讀上次建立的Cartfile.resolved檔案,來抓frameworks。

2016年2月17日 星期三

NSLog出所在的function名稱

//在obj-c的NSLog中可以用c++的__PRETTY_FUNCTION__
NSLog(@"%s", __PRETTY_FUNCTION__);
//show
-[YourCurrentClassName yourCurrentFunctionName:]

//在swift中還不行用,stackOverFlow上有人提出這個方式來解決
func pretty_function(file:String = __FILE__, function:String = __FUNCTION__, line:Int = __LINE__) {
    print("file:\(file.lastPathComponent) function:\(function) line:\(line)")
}
P.S. 現在swift的String已經把lastPathComponent拿掉,所以要改為下面的方式
func pretty_function(file:String = __FILE__, function:String = __FUNCTION__, line:Int = __LINE__) {
    print("file:\((file as NSString).lastPathComponent) function:\(function) line:\(line)")
}

#import V.S. @import

在c++中只有#include,這個預處理指定可以幫你把另一個.h的內容都複製一份過來你的檔案中,但這會有一個問題,你可以重覆複製了多份相同的.h過來。
於是obj-c中新增了#import來解決這個問題,#import會在複製過來.h之前先判斷是否已經複製過了,這樣一來可以保證你的檔案中只會有一份其他的.h。

但是這樣一來還是有個問題沒有解決:相同的.h引用到太多不同的檔案,造成編譯上的時間浪費,也會增加檔案的容量。

既然如此,那為什麼不找一個地方,先把這些要引用的.h先編譯好,放過去直接給你的檔案呼叫呢?
.pch檔就是在做這種事,你可以把你要預編譯的.h都放在這裡,在你的每一個檔案中都可以直接使用,這樣就可以加快編譯的速度。
但這裡又有一個問題,如果你的每一個.h都可以被你的任一檔案給呼叫,那你引入的module間的關係不就大亂了嗎?
於是Apple加入了Modules機制,LLVM5.0中加入了@import這個新語法,讓你可以像用#import一樣引入不同的.h,但這些.h的module都是被預編譯好的等著你來呼叫,如此一來既加快了編譯的速度,又可以保持引用.h的安全性。

P.S. 目前這個Modules機制僅適用於Apple原生的module(不適用第三方或是你自己編寫的module)。
P.S. 在iOS 7 SDK以上,預設使用Modules機制並且會將你的#import轉成@import