ラベル Swift の投稿を表示しています。 すべての投稿を表示
ラベル Swift の投稿を表示しています。 すべての投稿を表示

2021年10月3日日曜日

UITabBarAppearanceを使って、TabBarの配色をセットする

iOS13以降のTabBarのカスタマイズです。
今回の記事では、次のようにTabBarをカスタマイズしました。

1. UITabBarを継承した、カスタムViewを作成する
2. TabBarItemの配色をUITabBarItemAppearanceで作成する
3. UITabBarAppearanceを生成する
4. UITabBarにセットする
5. UITabBarViewControllerにて、カスタムViewを使う


UITabBarを継承した、カスタムViewは次になります。

final class MyUITabBar: UITabBar {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    private enum Const{
        static let backgroundColor: UIColor = .systemGroupedBackground
        static let tintColor: UIColor = .gray
        static let selectedColor: UIColor = .black
    }
    
    private func setupView() {
        let tabBarItemAppearance = setupTabBarItemAppearance()
        let appearance = UITabBarAppearance()
        appearance.configureWithOpaqueBackground()
        appearance.backgroundColor = Const.backgroundColor
        appearance.stackedLayoutAppearance = tabBarItemAppearance
        appearance.inlineLayoutAppearance = tabBarItemAppearance
        appearance.compactInlineLayoutAppearance = tabBarItemAppearance

        standardAppearance = appearance
        // iOS15: we need to set
        scrollEdgeAppearance = appearance
    }

    private func setupTabBarItemAppearance() -> UITabBarItemAppearance {
        let tabBarItemAppearance = UITabBarItemAppearance()
        // for normal
        tabBarItemAppearance.normal.iconColor = Const.tintColor
        tabBarItemAppearance.normal.titleTextAttributes = [NSAttributedString.Key.foregroundColor: Const.tintColor]
        
        // for selected
        tabBarItemAppearance.selected.iconColor = Const.selectedColor
        tabBarItemAppearance.selected.titleTextAttributes = [NSAttributedString.Key.foregroundColor: Const.selectedColor]
        
        return tabBarItemAppearance
    }
}

UITabBarControllerの子となる各ViewControllerでは、次のようにtabBarItemを指定するのみです。
tabBarItemと配色をするAppearanceをコード上で分け、責務が分離できるので非常に見やすいコードとなりました。

final class FirstViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setupTabBar()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    private func setupTabBar() {
        tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house"), selectedImage: UIImage(systemName: "house"))
    }
}

final class SecondViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setupTabBar()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    private func setupTabBar() {
        tabBarItem = UITabBarItem(title: "Trash", image: UIImage(systemName: "trash"), selectedImage: UIImage(systemName: "trash"))
    }

}

2021年9月26日日曜日

リンクタップ可能でかつ選択不可なUITextViewを作る

次なような、UITextViewを作成することにしました。

1. リンクタップは可能である、その他のテキストは操作無反応とする
2. ダブルタップによるテキスト選択動作は不可である
3. ロングタップは不可である


UITextViewのカスタムViewを作成する

条件1から3まで満たすようなカスタムViewを作ります。
この方法は、Stackoverflowに書かれているものを参考にしました。
UITextView: Disable selection, allow links


class UnselectableTextView: UITextView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let position = closestPosition(to: point) else { return false }
        guard let range = tokenizer.rangeEnclosingPosition(
                position,
                with: .character,
                inDirection: UITextDirection(rawValue: UITextLayoutDirection.left.rawValue)
              ) else { return false }
        let startIndex = offset(from: beginningOfDocument, to: range.start)
        return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil
    }

    override func becomeFirstResponder() -> Bool {
        return false
    }
}


override func point() にて、タップした位置のテキストがリンクであるかどうか判定し、override func becomeFirstResponder() にてダブルタップとロングタップを抑制しています。
ViewControllerに実装するときは、次のようになります。


class ViewController: UIViewController {

    @IBOutlet weak var textView: UnselectableTextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
    }

    private func setupViews() {
        textView.delegate = self
        textView.isEditable = false
        textView.isSelectable = true
        textView.backgroundColor = .clear

        let attributedString = NSMutableAttributedString(string: "this textview has a link test.")
        attributedString.addAttribute(.link, value: "https://www.google.com", range: NSRange(location: 20, length: 4))
        textView.attributedText = attributedString
    }
}

extension UIViewController: UITextViewDelegate {
    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        print(URL.absoluteString)
        return false
    }
}


ロングタップをハンドリングする

上記で作成したUnselectableTextViewに、独自の UILongPressGestureRecognizer を組み込もうとしたが断念。
UITextViewの GestureRecognizer とConflictしてしまい、良い感じで実装できずでした。
代替案として、UnselectableTextViewの後ろにUIViewを配置して、このUIViewにてロングタップをハンドリングさせます。


class ViewController: UIViewController {
    private func setupGesture() {
        let longPressGesture = UILongPressGestureRecognizer(
            target: self,
            action: #selector(ViewController.longPress(_:))
        )
        longPressGesture.delegate = self
        backgroundView.addGestureRecognizer(longPressGesture)
    }
}

extension UIViewController: UIGestureRecognizerDelegate {
    @objc func longPress(_ sender: UILongPressGestureRecognizer) {
        print(sender.state.rawValue.description)
    }
}

2019年8月17日土曜日

UIScreenEdgePanGestureRecognizerをプログラムで実装する

ViewControllerに実装する


storyboardではなく、プログラムでEdgeスワイプを実装してみました。
UIScreenEdgePanGestureRecognizerをViewController.viewに追加して、selectorを使ってメソッドでハンドリングします。


class ViewController: UIViewController {

    private let closer = SwipeEdgeCloser()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let edgePan = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleScreenEdgeSwiped))
        edgePan.edges = .left
        view.addGestureRecognizer(edgePan)
    }

    @objc func handleScreenEdgeSwiped(_ recognizer: UIScreenEdgePanGestureRecognizer) {
        if recognizer.state == .recognized {
            print("Screen edge swiped!")
        }
    }
}



クラスに実装する


ViewControllerがファットになりそうなので、クラス化してみました。処理はほぼ同様で、selectorを新しく作成したSwipeLeftEdgePanGestureのメソッドを指定しています。

class ViewController: UIViewController {

    private let closer = SwipeLeftEdgePanGesture()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addGestureRecognizer(closer.panGesture)
    }
}

final class SwipeLeftEdgePanGesture {
    let panGesture: UIScreenEdgePanGestureRecognizer
    
    init() {
        panGesture = UIScreenEdgePanGestureRecognizer()
        panGesture.edges = .left
        panGesture.addTarget(self, action: #selector(SwipeLeftEdgePanGesture.screenEdgeSwiped))
    }
    
    @objc private func screenEdgeSwiped(_ recognizer: UIScreenEdgePanGestureRecognizer) {
        if recognizer.state == .recognized {
            print("SwipeLeftEdgePanGesture: Screen edge swiped!")
        }
    }
}

2016年5月22日日曜日

iOSでFirebase Analyticsを使う


(引用元:https://firebase.google.com)

セットアップ

公式Page
https://firebase.google.com/docs/ios/setup

CocoaPodを利用してインストールします。
podの初期設定を実行。
pod init
Podfile を開いて以下を追加します。
pod 'Firebase/Core'
podのインストールを実行します。
pod install

Analytics以外の機能を使用するには、次の公式サイトに記載されているpodを追加しましょう。
https://firebase.google.com/docs/ios/setup#available_pods


import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // init Firebase
        FIRApp.configure()
        return true
    }
}


イベント送信方法

以下、宣言済みのイベントがあり、ヘッダーファイルに定義されているので参照。
イベント:FIReventNames.h
パラメータ:FIRParameterNames.h


FIRAnalytics.logEventWithName(kFIREventSelectContent, parameters: [
  kFIRParameterContentType:"cont",
  kFIRParameterItemID:"1"
  ])


ユーザープロパティ

アプリのユーザーをカテゴライズすることが可能です。
FIRAnalytics.setUserPropertyString(food, forName: "favorite_food")

2016年4月28日木曜日

[RxSwift] SectionありのDataSourceを生成する

本家のサンプルにも記載されている通り、RxDataSourcesを使用する方法があります。
https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxDataSources

RxDataSources:
https://github.com/RxSwiftCommunity/RxDataSources

Podfileに記載するなりしてインストールしてください。

SectionありのdataSourceを生成する

RxTableViewSectionedReloadDataSourceを使用します。SectionModelでSectionに表示するelementを指定します。

let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, String>>()
let items = Observable.just([
    SectionModel(model: "First section", items: [
            "aaaa",
            "bbbb",
        ]),
    SectionModel(model: "Second section", items: [
            "cccc",
            "dddd",
        ])
    ])


セルの生成

以下のように、indexPathとelementが引数に入っているのでコールバック内でセルを生成する。
dataSource.configureCell = { (_, tableView, indexPath, element) in
    let cell = tv.dequeueReusableCellWithIdentifier("Cell")!
    cell.textLabel?.text = "\(element) @ row \(indexPath.row)"
    return cell
}


データとTablewViewをBindする

items
    .bindTo(tableView.rx_itemsWithDataSource(dataSource))
    .addDisposableTo(disposeBag)


ヘッダーのカスタマイズ

UITableViewDelegateで処理する必要があります。
以下のように、ヘッダー用のViewとHightを返すモジュールを実装します。
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let view = UITableViewHeaderFooterView()
    view.textLabel?.text = dataSource.sectionAtIndex(section).model ?? ""
    return view
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 44
}

2016年4月12日火曜日

[Swift]循環参照(相互参照)によるメモリリーク

MVVM移行時にクラス分けを行った結果、ViewControllerのメモリリークが発生しました。deinitが呼ばれず期待動作となりませんでした。


循環参照(相互参照)によるメモリリーク

以下のような状態になると、メモリリークが発生します。
何も考えずにViewModelのDelegateを追加、DelegateにViewControllerをセットでメモリリーク発生


解決策

弱参照になるようにDelegate用プロトコルをclass継承し、weakで保持できるように対応。


protocolにclass継承追加
 public protocol ViewModelDelegate : class{
 }


weak追加
 weak var delegate: ViewModelDelegate?

2016年4月3日日曜日

[Swift]UIButtonでPressed状態の装飾を行う

AndroidならSelectorのxmlファイルを作成すればボタンの状態に合わせた装飾できます。
Swiftではボタンの文字などは次のfuncで設定可能です。

func setTitle(_ title: String?,forState state: UIControlState)
func setTitleColor(_ color: UIColor?,forState state: UIControlState)
func setTitleShadowColor(_ color: UIColor?,forState state: UIControlState)
func setBackgroundImage(_ image: UIImage?,forState state: UIControlState)

layerなどで装飾をする場合は、次のようにhighlightedのdidSetで変更可能です。

@IBDesignable class MyUIButton: UIButton {
    
    @IBInspectable var borderColor :  UIColor = UIColor.blackColor()
    @IBInspectable var borderHighLightedColor :  UIColor = UIColor.clearColor()
    
    override internal func awakeFromNib() {
        super.awakeFromNib()
    }
    
    override var highlighted: Bool{
        didSet{
            if (highlighted) {
                self.layer.borderColor = borderHighLightedColor.CGColor
            } else {
                self.layer.borderColor = borderColor.CGColor
            }
        }
    }
    // Attributes Inspectorで設定した値を反映
    override func drawRect(rect: CGRect) {
        self.layer.borderColor = borderColor.CGColor
    }

}

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で値を受け取ります。

2015年10月22日木曜日

[Swift][GCM]iOSでGoogle Cloud Messageを使う

iOSでGoogle Cloud Message 3.0を使ったので、はまりどころのメモです。
基本的なことは本家サイトを参照ください。
https://developers.google.com/cloud-messaging/ios/client?ver=swift

APNs用にデータを追加する

データ構造はGCM 2.0に以下のAPNsでも使用できるパラメータを追加すればデバイスに届きます。
  "content_available" : "true"
  "notification" : {
    "body" : "great match!",
    "title" : "Portugal vs. Denmark"
    }

もし、デバイスに届かない場合はcontent_availableの値を確認しましょう。"true"というStringではなく、true / falseのBoolが正しいです。

Push受信しても鳴動しない

notificationにsoundパラメータを追加しましょう。
https://developers.google.com/cloud-messaging/http-server-ref
https://developer.apple.com/jp/documentation/RemoteNotificationsPG.pdf

アプリ起動していない状態でバックグラウンド受信しない

priorityの追加
priorityパラメータを追加して、10を指定しましょう。
https://developers.google.com/cloud-messaging/http-server-ref
https://developer.apple.com/jp/documentation/RemoteNotificationsPG.pdf
iOSアプリ側の設定
Background fetch modeをONにしましょう。
AppDelegateにて、MinimumBackgroundFetchIntervalをセットします。DefaultはNeverになっているため、Background fetchしません。
  application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)

2015年10月14日水曜日

[Swift][GoogleAnalytics]CustomDimensionを追加してイベント送信する

Google Analyticsのイベント送信時にCustomDimensionを追加する方法です。

        let tracker = GAI.sharedInstance().defaultTracker
        let params = GAIDictionaryBuilder.createEventWithCategory(
            category,
            action: action,
            label: label,
            value: value)
            .set(_corpId, forKey: GAIFields.customDimensionForIndex(1))
            .build() as [NSObject : AnyObject]
        
        tracker.send(params)


set(_corpId, forKey: GAIFields.customDimensionForIndex(1))でCustomDimensionの要素を追加しています。

2015年8月21日金曜日

iOS8.xでUITableViewAutomaticDimensionを使うと、セル位置が正常にならない

iOS8.xでTableViewのセル高さの自動調整が可能です。

    self.tableView.rowHeight = UITableViewAutomaticDimension



ただし、TableViewからセル選択により画面遷移後、戻ってきたときなど再表示をするとセル位置が調整されていないことがあります。 対策として、一度表示したセルの高さを保持しておいて、estimatedHeightForRowAtIndexPathで値を返します。

    var cellHeight:Dictionary = ["":0]
    
    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        
        cell.contentView.updateConstraints()
        self.cellHeight[String(indexPath.item )] = Float(cell.frame.size.height)
        
    }
    
    override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if(self.cellHeight[String(indexPath.item )] != nil){
            return CGFloat(self.cellHeight[String(indexPath.item )]!)
        }
        return self.tableView.estimatedRowHeight
        
    }

2015年2月26日木曜日

Swiftでパラメータ付きPOSTリクエストを行う

SwiftでWebAPIのリクエストを行う際、POSTメソッドを使う必要がありました。
次は非同期でリクエストを行う、NSURLConnectionクラスのsendAsynchronousRequestメソッドを用いたサンプルソースです。

        // URLセット
        let url = NSURL(string: "https://test.url.jp")
        var request : NSMutableURLRequest = NSMutableURLRequest(URL: url!)

        // POSTメソッド指定
        request.HTTPMethod = "POST"
        
        // POSTパラメータ
        var bodyData: String = "key1=value1&key2=value2"
        request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
        // ヘッダの指定
        request.setValue("HeaderValue", forHTTPHeaderField: "HeaderName")
        
        //asyncで実行
        NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: responseHandler)

2015年2月24日火曜日

SwiftなCocoa Touch FrameworkでCommonCryptoを使う

Swiftを使ってCocoa Touch Frameworkを作成しています。
どうしても、CommonCryptoを使う必要があり手段を探していました。

以下、参考になったサイトです。
Importing CommonCrypto in a Swift framework
CommonHMAC in Swift

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

  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 *
}

このあたり、公式Developerサイトに書いてるのかな?

2015年2月21日土曜日

Unit testで非同期処理のWaitをする

XcodeでiOS開発の勉強をはじめました。
いきなりですが、UnitTestネタです。テストしないもの、エンジニアにあらずです。

参考:Writing Test Classes and Methods

waitForExpectationsWithTimeoutで非同期処理の待ち状態を指定

標準テストフレームワークのXCTestExpectationとXCTestCase.waitForExpectationsWithTimeoutを組み合わせて実装します。
手順は次のようになります。

  1. XCTestCase.expectationWithDescriptionをコールして、XCTestExpectationを取得 
  2. XCTestCase.waitForExpectationsWithTimeoutで、waitを指定 
  3. 非同期処理終了のタイミングで、XCTestExpectation.fulfill()。もし、waitForExpectationsWithTimeoutで指定した時間内にコールしなければfailとなる

サンプルプログラムです。
    func testPerformAsyncRequest(){
        // XCTestExpectationの取得
        let expectation = self.expectationWithDescription("client key")

        // 非同期処理のコールバック処理完了後、expectation.fulfill()をコール
        expectation.fulfill()
        
        //waitの時間指定
        self.waitForExpectationsWithTimeout(5, handler: nil)
        
    }