2016年3月27日日曜日

[Swift] Keyboardの表示に合わせてScrollViewの高さを変更する

Androidの場合、Keyboardが表示されると自動的にアプリケーションWIndowがリサイズされてKeyboardに被らないように表示されます。
iOSでは自前で実装する必要があります。

以下は、ScrollVIewを利用したサンプルです。
Keyboardが表示されたタイミングで、ScrollViewのcontentInsetとscrollIndicatorInsetsを変更します。

class AdjustScrollViewControll : NSObject{
    var scrollView : UIScrollView?
    
    init(scrollView : UIScrollView){
        self.scrollView = scrollView
    }
    
    func addObservers(){
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: #selector(self.willShowNotification(_:)), name: UIKeyboardWillShowNotification, object: nil)
        notificationCenter.addObserver(self, selector: #selector(self.willHideNotification(_:)), name: UIKeyboardWillHideNotification, object: nil)
    }
    
    func removeObserviers(){
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
    
    func willShowNotification(notification: NSNotification) {
        
        let info = notification.userInfo
        let infoNSValue = info![UIKeyboardFrameEndUserInfoKey] as! NSValue
        let kbSize = infoNSValue.CGRectValue().size
        let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height + 8, 0.0)
        scrollView!.contentInset = contentInsets
        scrollView!.scrollIndicatorInsets = contentInsets
    }
    
    func willHideNotification(notification: NSNotification) {
        let contentInsets = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)
        scrollView!.contentInset = contentInsets
        scrollView!.scrollIndicatorInsets = contentInsets
    }
}


ViewControllerからは次のようにコールします。
class HiddingKeyboardViewController: UIViewController,UITextFieldDelegate {

    @IBOutlet weak var scrollView: UIScrollView!
    
    var adjustTextFieldControll : AdjustScrollViewControll?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        adjustTextFieldControll = AdjustScrollViewControll(scrollView: scrollView)
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        adjustTextFieldControll!.addObservers()
    }
    
    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        adjustTextFieldControll!.removeObserviers()
    }
    
}

2016年3月26日土曜日

[Swift]アスペクト比を維持しつつUIImageViewのサイズを変更する

TableViewCellの実装で、次のような画面幅に合わせた画像を表示する際に困ったのでメモ。



Androidでは次のように指定することで、高さが自動で確定することができました。

<ImageView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:adjustViewBounds="true"/>

が、iOSでは簡単にはできず、カスタムViewを作ることで解決しました。
UIImageViewのimageがセットされる際に、アスペクト比を計算してConstraintを確定しているだけです。

class AspectAdjuestUIImageView: UIImageView {
    
    internal var aspectConstraint : NSLayoutConstraint? {
        didSet {
            if oldValue != nil {
                self.removeConstraint(oldValue!)
            }
            if aspectConstraint != nil {
                self.addConstraint(aspectConstraint!)
            }
        }
    }
    
    override var image: UIImage?{
        willSet{
            let aspect = newValue!.size.width / newValue!.size.height
            
            aspectConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Height, multiplier: aspect, constant: 0.0)
        }
    }
}

2016年3月23日水曜日

SwiftなCocoa Touch FrameworkでCommonCryptoを使う for iOS9.3

SwiftでCommonCryptoを使用する際、libcommonCryptoライブラリのリンクエラーが出たので対応方法を。

手順は次のようになります。

  1. CommonCryptoというディレクトリを作成
  2. module.mapというファイルを作成
  3. プロジェクト設定のBuild Settings -> Swift Compiler - Search Paths -> Import Pathsに、上記のCommonCryptoディレクトリを指定



次はサンプルです。 module.mapにSDK内のヘッダファイルのPathを記述します。

module CommonCrypto [system] {
    header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/CommonCrypto/CommonCrypto.h"
-    link "CommonCrypto"
    export *
}

link "CommonCrypto"を削除します。

2016年3月19日土曜日

[Swift]TableViewのSection HeaderをカスタムViewに置き換える

TableViewのSection HeaderをカスタムViewに置き換える方法です。



UITableViewHeaderFooterViewのサブクラスを作成

UITableViewHeaderFooterViewを継承したクラスを作成します。

import UIKit

class CustomTableViewHeaderFooterView: UITableViewHeaderFooterView {

    @IBOutlet weak var headerView: UIView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
}


Nibファイルを作成

Header用のViewを作成します。



TableViewにセット

Cellと同様にtableView.registerNibでセットしておきます。tableView:viewForHeaderInSectionでロードして戻り値とします。
注意点としては、ViewのサイズがAutolayoutになっていないので、frame.sizeでサイズ指定します。

class ViewController: UITableViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let nib:UINib = UINib(nibName: "CustomTableViewHeaderFooterView", bundle: nil)
        tableView.registerNib(nib, forHeaderFooterViewReuseIdentifier: "CustomTableViewHeaderFooterView")
    }
    
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    // header height
    override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44
    }
    
    
    // header view
    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header :CustomTableViewHeaderFooterView = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("CustomTableViewHeaderFooterView") as! CustomTableViewHeaderFooterView
        header.headerView.frame.size = CGRectMake(0, 0, tableView.frame.size.width, 44).size
        
        return header
    }
    
}

[Swift]iOSでGoogle Places API for iOSを使う

Google Places APIをiOSで使う方法です。
公式サイトはこちらです。

Google Places API for iOS
https://developers.google.com/places/ios-api/?hl=ja


SDKの追加

SDKはCocoaPodで公開されています。Podfileに次の項目を追加します。

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.1'
pod 'GoogleMaps'
Podfileに追加の上、installを実行するだけで完了です。

pod install


API Keyの取得

Google Developer Consoleにて、APIの設定が必要です。次はAPI Keyを取得するまでの手順です。

  • プロジェクトを作成
  • Google Places API for iOS と Google Maps SDK for iOSを有効にする
  • 認証情報からiOSキーを作成(BundleIDの登録が必要です)
  • API Keyを取得する



API Keyのセット

AppDelegate.swiftで、GMSServicesクラスにAPI Keyのセットします。
import GoogleMapsが必要です。

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        
        GMSServices.provideAPIKey("YOUR_API_KEY")
        return true
    }


Place Autocompleteの使い方

ViewControllerからGMSAutocompleteViewControllerを起動します。
オートコンプレートによるPlaceの選択結果は、GMSAutocompleteViewControllerDelegateで受け取ることができます。

extension ViewController: GMSAutocompleteViewControllerDelegate {
    func startGooglePlacesAutocomplete(){
        let autocompleteController = GMSAutocompleteViewController()
        autocompleteController.delegate = self
        self.presentViewController(autocompleteController, animated: true, completion: nil)
    }
    
    // Handle the user's selection.
    func viewController(viewController: GMSAutocompleteViewController, didAutocompleteWithPlace place: GMSPlace) {
        print("Place name: ", place.name)
        print("Place address: ", place.formattedAddress)
        print("Place attributions: ", place.attributions)
        self.dismissViewControllerAnimated(true, completion: nil)
    }
    
    func viewController(viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: NSError) {
        // TODO: handle the error.
        print("Error: ", error.description)
    }
    
    // User canceled the operation.
    func wasCancelled(viewController: GMSAutocompleteViewController) {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
    
    // Turn the network activity indicator on and off again.
    func didRequestAutocompletePredictions(viewController: GMSAutocompleteViewController) {
        UIApplication.sharedApplication().networkActivityIndicatorVisible = true
    }
    
    func didUpdateAutocompletePredictions(viewController: GMSAutocompleteViewController) {
        UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    }
    
}


GMSAutocompleteViewControllerの起動が成功すると、次のような画面が表示されます。Placesの確定時にdidAutocompleteWithPlaceがコールされるので、引数:placeで値を受け取ります。

2016年3月5日土曜日

BottomSheetの実装方法 via Android Design Support Library 23.2

Android Design Support Library 23.2でBottomSheetが追加されました。
GoogleMapなどに用いられている画面が作成可能になりました。
実装はCoordinatorLayoutの子Viewに対して、Behaviorを指定するだけとシンプルです。

AOSPのソースコード : BottomSheetBehavior.java

Layoutのサンプル

BottomSheetはBehaviorを指定することで実装します。
CoordinatorLayoutの子ViewとしてLayoutを追加して、BottomSheet化したいLayoutに対してapp:layout_behavior="@string/bottom_sheet_behavior"を指定します。

<android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            app:layout_behavior="@string/bottom_sheet_behavior"
            app:behavior_peekHeight="240dp"
            app:behavior_hideable="false"
            android:background="@android:color/white">
        <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="BottomSheet Sample"/>
    </LinearLayout>
</android.support.design.widget.CoordinatorLayout>

次のパラメータの指定が可能です。

  • app:behavior_peekHeight
    • BottomSheetの最小表示サイズ
  • app:behavior_hideable
    • 下スクロールで完全に非表示にするかどうか。非表示後に上スクロールで表示可能


2016年3月2日水曜日

[Swift]NavigationControllerのNavigationBarの表示 / 非表示方法

NavigationControllerを使っている際、画面上部のBarを表示したくないことがあります。
viewDidLoad()で表示/非表示するのではなく、viewWillAppear()とviewWillDisappear()で表示切り替えを行い、
画面遷移後のControllerに処理させないようにしました。

    override func viewWillAppear(animated: Bool) {
        self.navigationController?.navigationBarHidden = true
    }
    
    override func viewWillDisappear(animated: Bool) {
        self.navigationController?.navigationBarHidden = false
    }