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

Xcode8 Swift3への対応

Swift3へのConvertでエラーとなって手動で直した部分


UICollectionDataSource

-    override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
+    override func numberOfSections(in collectionView: UICollectionView) -> Int {


-    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {


-    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
+    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {



UITableViewDataSource


-    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
+    override func numberOfSections(in tableView: UITableView) -> Int {


-    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {


-    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {



-    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
+
+    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {



CGGeometory

-        frame = CGRectMake(0.0, 0.0, self.view.frame.width, self.view.frame.height)
+       frame = CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height)


UIVIewController

-     override func shouldAutomaticallyForwardAppearanceMethods() -> Bool {
+    override var shouldAutomaticallyForwardAppearanceMethods: Bool {



Cocoapods

"Use Legacy Swift Language Version"のエラーが出た場合、該当のフレームワークのBuild Settingsで「Use Legacy Swift Language Version」の設定をNOにする



Swift、UILabelで表示する文字列の高さを取得する

UILabelの高さ取得に気をつけること

1. xibやstoryboradの指定したFontと、プログラムで指定しているフォントは同じかどうか
2. viewDidLoad時ではまだviewのサイズが決まっていない

ロジック

NSString
func boundingRectWithSize(size: CGSize, options: NSStringDrawingOptions, attributes: [NSObject : AnyObject]!, context: NSStringDrawingContext!) -> CGRect

NSAttributedString
func boundingRectWithSize(size: CGSize, options: NSStringDrawingOptions, context: NSStringDrawingContext?) -> CGRect


どちらも使い方は同じ、必要な最大領域、オプションを指定すれば、描画に必要なサイズが取得可能

サンプル

NSString

    func heightWithText(text: String) -> CGFloat {
       
        let horizonMergin:CGFloat = 32
        let verticalMergin:CGFloat = 32
        
        let maxSize = CGSize(width: CGRectGetWidth(UIScreen.mainScreen().bounds) - horizonMergin, height: CGFloat.max)
        let options = unsafeBitCast(
            NSStringDrawingOptions.UsesLineFragmentOrigin.rawValue |
            NSStringDrawingOptions.UsesFontLeading.rawValue,
            NSStringDrawingOptions.self)
        
        // ここは必要に応じて
        var paragrahStyle = NSMutableParagraphStyle()
        paragrahStyle.lineHeightMultiple = 1.3
        paragrahStyle.lineSpacing = 4

        let font = UIFont.systemFontOfSize(14.0)
        var attributes = [NSFontAttributeName:font,
            NSParagraphStyleAttributeName:paragrahStyle]
        
        let frame = text.boundingRectWithSize(maxSize,
            options: options,
            attributes: attributes,
            context: nil)
        let height = ceil(frame.size.height) + verticalMergin
        
        return height
    }

NSAttributedString

    class func heightWithText(text: String) -> CGFloat {
        
        let horizonMergin:CGFloat = 32
        let verticalMergin:CGFloat = 32
        
        var attr = NSMutableAttributedString(string: text)
        
        var paragrahStyle = NSMutableParagraphStyle()
        paragrahStyle.lineHeightMultiple = 1.3
        paragrahStyle.lineSpacing = 4
        
        attr.addAttribute(NSParagraphStyleAttributeName, value: paragrahStyle, range: NSMakeRange(0, attr.length))
        
        
        let maxSize = CGSize(width: CGRectGetWidth(UIScreen.mainScreen().bounds) - horizonMergin, height: CGFloat.max)
        let options = unsafeBitCast(
            NSStringDrawingOptions.UsesLineFragmentOrigin.rawValue |
                NSStringDrawingOptions.UsesFontLeading.rawValue,
            NSStringDrawingOptions.self)
        
        let font = UIFont.systemFontOfSize(14.0)
        attr.addFontAttribute(font, range: NSRange(location: 0, length: attr.length))
        
        let frame = attr.boundingRectWithSize(maxSize,
            options: options,
            context: nil)
        let height = ceil(frame.size.height) + verticalMergin
        
        return height
    }

パフォーマンス

NSString : 0.000555038452148438 0.000557005405426025
NSAttributedString : 0.000142991542816162 0.000165998935699463

パフォーマンスはNSAttributedStringの方が早いです。

Swiftで絵文字入りの文字列操作


文字列の扱い

Swiftで文字列はNSStringからStringクラスに変更された

文字数

これは結構ネットでもあるのでいいと思う
var str = "Hello"
var len = countElements(str) // 5

文字列の置換

x文字列目まで、x文字以降などを取得するsubstringToIndexsubstringFromIndexなどは注意が必要
(str as NSString).substringFromIndex(3)
絵文字がなければ特に問題ないのだが、絵文字が入るとうまく取得できない

advanceを使ってIndexを取得しそれを使う

var index = advance(str.startIndex, 3)
var str2 = str.substringToIndex(index)


UINavigationControllerをスワイプで遷移させてみる

ナビゲーションコントローラを滑らかに遷移させる

ナビゲーションコントローラの画面遷移をカスタマイズする

主要なクラス

UIViewControllerAnimatedTransitioning

具体的なアニメーションを定義するクラス。実行時間やviewの動きなど。

UIViewControllerInteractiveTransitioning

画面遷移の進捗を把握するクラス?途中経過や中止、完了などを教えてあげれば良きに計らってくれる。 UIPercentDrivenInteractiveTransitionを使うと楽

NavigationControllerDelegate

// pushやpopされたコントローラが渡されるので、適切なアニメーション定義クラスを返す
func navigationController(
  navigationController: UINavigationController,
  animationControllerForOperation operation: UINavigationControllerOperation,
  fromViewController fromVC: UIViewController,
  toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
}

// ここは何も気にせずにUIPercentDrivenInteractiveTransitionを返すが良い
func navigationController(navigationController: UINavigationController,
   interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
}

実装方法

今回はナビゲーションバーにおけるスワイプでのページ切り替えなので、NavigationControllerを継承したクラスでやってみる

アニメーションの定義

わかりやすく、PushとPopで分けて書く

class PushAnimatedTransitioning : NSObject, UIViewControllerAnimatedTransitioning {

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

        // 遷移元のVC
        var fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
        // 遷移先のVC
        var toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!

        // 表示中のView
        var containerView = transitionContext.containerView()


        var duration:NSTimeInterval = self.transitionDuration(transitionContext)



        // アニメーション終了時のframeを取得
        toViewController.view.frame = transitionContext.finalFrameForViewController(toViewController)

        // 右端から出すため初期値は幅分をプラスする
        toViewController.view.center.x += containerView.bounds.width

        containerView.addSubview(toViewController.view)


        UIView.animateWithDuration(duration,
            animations: { () -> Void in
                // 先ほどプラスした幅分を戻す
                toViewController.view.center.x -= containerView.bounds.width

            }, completion: { (Bool) -> Void in

                // キャンセルされていなければ完了
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled());
        })

    }

    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return 0.3
    }

}

SwipeNavigationController

delegate

override func viewDidLoad() {
    super.viewDidLoad()

    self.delegate = self
}

// 画面遷移するときに使われるアニメーションを返す
func navigationController(navigationController: UINavigationController,
    animationControllerForOperation operation: UINavigationControllerOperation,
    fromViewController fromVC: UIViewController,
    toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        if operation == .Push {
            return PushAnimatedTransitioning()
        }
        return nil
}

// UIPercentDrivenInteractiveTransitionを返す
func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return self.interactiveTransition
}

Pan gesture

UINavigationControllerのdelegateで設定。ここは特にどこでも良い

func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {

    var gesture = UIPanGestureRecognizer(target: self, action: "panGesture:")
    gesture.delegate = self
    viewController.view.addGestureRecognizer(gesture)
}

// 横スワイプのみ対応
func gestureRecognizerShouldBegin(gestureRecognizer: UIPanGestureRecognizer) -> Bool {
    var location = gestureRecognizer.translationInView(gestureRecognizer.view!)
    if self.nextViewController == nil { return false }
    return fabs(location.x) > fabs(location.y)
}

func panGesture(recognizer: UIPanGestureRecognizer) {
    var location = recognizer.translationInView(recognizer.view!)

    // どれくらい遷移したかを 0 〜 1で数値化
    var progress = fabs(location.x / (self.view.bounds.size.width * 1.0));
    progress = min(1.0, max(0.0, progress));

    // 次の画面が設定してなければ処理は継続しない
    if (self.nextViewController == nil) { return }

    if (recognizer.state == .Began) {
        // 左へのスワイプのみ
        if location.x > 0 { return }

        self.interactiveTransition = UIPercentDrivenInteractiveTransition()

        // ページ遷移させる!!!!!!
        self.pushViewController(self.nextViewController!, animated: true)
    }
    else if (recognizer.state == .Changed) {

        // 変化量を通知させる
        self.interactiveTransition?.updateInteractiveTransition(progress)
    }
    else if (recognizer.state == .Ended || recognizer.state == .Cancelled) {

        // 終了かキャンセルか
        if self.interactiveTransition != nil {
            if (progress > 0.5) {
                self.interactiveTransition?.finishInteractiveTransition()
                self.nextViewController = nil
            }
            else {
                self.interactiveTransition?.cancelInteractiveTransition()
            }
        }

        self.interactiveTransition = nil;
    }
}

使い方

storyboardなどでUINavigationControllerのClassをSwipeNavigationControllerにして、 スワイプで遷移させたいViewControllerを設定する。 callbackとかでもよかったけどちょっとめんどくさいかったので。。

    if let navi = self.navigationController as? SwipeNavigationController {
        navi.nextViewController = vc
    }

全体のコードはGithubで。

ReactNativeでAndroid対応する話

前提 ReactNativeでiOS版のアプリをリリースしていて、Android版をリリースする話 トラブルシューティング Build.VERSION_CODES.Q が存在しないエラー compileSdkVersionを29以上にすると解決 メモリー足りないエラー Execu...