20190707のSwiftに関する記事は6件です。

【Swift学習まとめ】 スコープ

スコープとは

変数、定数、関数、型の名前の有効範囲を表すもの
適用範囲に応じてグローバルスコープ、ローカルスコープに分類される
全ての変数、定数、関数、型はどちらかのスコープに属する

約束事

同じスコープ内には同じ名前を複数存在させることはできない
※変数、定数、関数、型の種類が異なっていても同様

スクリーンショット 2019-07-07 22.03.35.png

ローカルスコープ

関数や制御構文の内部で定義されるスコープ
外部からの参照はできず、関数や制御構文の実行文の内部でのみ有効のため
意図しない変更が起こりにくいメリットがある
スクリーンショット 2019-07-07 22.27.49.png

グローバルスコープ

関数、型宣言の外部で定義されるスコープ
宣言された変数や定数はファイル外を含めどこからでも参照可能だが、意図しない変更を招きやすいため、ローカルスコープでの宣言よりも説明的な命名が必要である
スクリーンショット 2019-07-07 22.24.48.png

参考文献

[改訂新版]Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 WEB+DB PRESS plus

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

StoryboardでScrollView

contenViewをxibに

1.contenViewをxibにする
2.ViewControllerにはコードでaddsubView&制約追加

contentViewをcontainerViewに

参考記事

StoryboardでUIScrollViewを用いる際、ContainerViewを使うと便利
Storyboard 上で UIScrollView を AutoLayout を使って設定する
@1amgeekさんのツイート

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ライフサイクル 優良記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】 RealmSwiftのマイグレーション(データ引き継ぎ有)方法

前置き

ネットの情報を色々見て、公式の記述サンプルも見たんですが、Realmのマイグレーションがうまく行かない。より正確には、schemaVersionを更新して、レコードフォーマットも更新されて、アプリが稼働することは確認できるのに、マイグレーション前のデータが引き継がれない。
とにかく「これでマイグレーションできました」っていうブログ等の書き方を片っ端から試していって、結果、下記の書き方でうまく行ったので備忘録として残します。

開発環境

端末:MacBook Pro/MacOS 10.14.5(Mojave)
Xcode:10.2.1
Swift:5

実装内容

ソースサンプル

RealmSwift.swift
import Foundation
import RealmSwift

class myRoutine: Object {
    @objc dynamic var column1 = String()
    @objc dynamic var column2 = String()
    @objc dynamic var column3 = String()
    @objc dynamic var column4 = String()
    @objc dynamic var column5 = String()
}

func realmMigration() {
    // Realmマイグレーションバージョン
    // レコードフォーマットを変更する場合、このバージョンも上げていく。
    let migSchemaVersion: UInt64 = 1

    // マイグレーション設定
    let config = Realm.Configuration(
        schemaVersion: migSchemaVersion,
        migrationBlock: { migration, oldSchemaVersion in
            if (oldSchemaVersion < migSchemaVersion) {
    }})
    Realm.Configuration.defaultConfiguration = config
}

ライフサイクルの早い段階でマイグレーション処理 realmMigration() を呼び出す

ViewController.swift
class ViewController: UIViewController {
    ...

    // 自分の場合は、作ってるアプリのライフサイクルで一番早いのが
    // awakeFromNib()だったのでここに書いています。
    override func awakeFromNib() {
        realmMigration()

        // realmを使う時のおまじない
        realm = try! Realm()

        ...
    }
    ...
}

感想、その他メモ

いろいろ試して、(そもそも何でいろいろな書き方があるのか、という話ですが)なんとかデータ引き継ぎできる書き方が見つかってよかったです。
もっと余裕と知識があれば、ダメだった書き方のダメな理由も調べたいんですが、それはまた別の機会に。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユーザーページの画像をアップロードする

このページの続きです。
スクリーンショット 2019-07-07 9.30.50.png ⇨ スクリーンショット 2019-07-07 9.36.49.png
これが完成形です。

事前に

❶画像を拾ってくる。
❷画像をNCMBにアップロード
❸画像をユーザーページに読み込む。
の手順でやっていきたいと思います。

スクリーンショット 2019-07-06 9.22.05.png

まずは部品の接続、TextField,TextViewのDelegate接続、userIdTextFieldに現在ログインしているユーザーの取得を行なっていきます。

import UIKit
import NCMB

class EditProfileViewController: UIViewController,UITextViewDelegate,UITextFieldDelegate {

    @IBOutlet weak var userImageView: UIImageView!

    @IBOutlet weak var userIdTextField:UITextField!

    @IBOutlet weak var userNameTextField: UITextField!

    @IBOutlet weak var introductionTextView: UITextView!
    override func viewDidLoad() {
        super.viewDidLoad()

        userIdTextField.delegate = self
        userNameTextField.delegate = self
        introductionTextView.delegate = self

        let userId = NCMBUser.current()?.userName
        userIdTextField.text = userId

    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

    func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
        textView.resignFirstResponder()
        return true
    }

    @IBAction func close(_ sender: Any) {
        self.dismiss(animated: true, completion: nil)
    }

}

以下のような挙動になると思います。
スクリーンショット 2019-07-06 8.59.35.png

そしてここからカメラとフォトライブラリーの取得に入っていきます。
またその機能に移る前に、iOS10以降ではプライバシーを扱う前にInfo.plistに書かなければいけないことがあります。
スクリーンショット 2019-07-06 9.13.59.png
赤色で囲んでいるところを追加し、適当にカメラとフォトライブラリを使うときの文言を書いてください。

UIImagePickerController

EditProfileViewController.swift
~~~省略~~~
@IBAction func selectImage(_ sender: Any) {

        let alertController = UIAlertController(title: "画像の選択", message: "選択してください", preferredStyle:.actionSheet)

        let cameraAction = UIAlertAction(title: "カメラ", style: .default) { (action) in
            // カメラ起動
            //カメラが使えたら
            if UIImagePickerController.isSourceTypeAvailable(.camera) == true {
                let picker = UIImagePickerController()
                picker.sourceType = .camera
                picker.allowsEditing = true
                picker.delegate = self
                self.present(picker, animated: true, completion: nil)
            } else {
                print("この機種ではカメラが使用出来ません。")
            }
        }
        let albumAction = UIAlertAction(title: "フォトライブラリ", style: .default) { (action) in
            // アルバム起動
            if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) == true {
                let picker = UIImagePickerController()
                picker.sourceType = .photoLibrary
                picker.allowsEditing = true
                picker.delegate = self
                self.present(picker, animated: true, completion: nil)
            } else {
                print("この機種ではフォトライブラリが使用出来ません。")
            }
        }
        let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { (action) in
            alertController.dismiss(animated: true, completion: nil)
        }
        alertController.addAction(cameraAction)
        alertController.addAction(albumAction)
        alertController.addAction(cancelAction)
        self.present(alertController, animated: true, completion: nil)

    }

スクリーンショット 2019-07-06 9.19.40.png

次に選ばれた画像をImageViewに表示するという処理を作っていきます。
とその前に、縦長や横長の画像を表示するために一手間加えます。
UIImageViewを選択した状態で、
スクリーンショット 2019-07-06 9.28.48.png
ContentModeをAspectFillに、またCliptoBoundsにチェックを入れてください。

EditProfileViewController.swift
~~~省略~~~
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage

        userImageView.image = selectedImage

        picker.dismiss(animated: true, completion: nil)
    }
~~~省略~~~

スクリーンショット 2019-07-06 9.33.55.png
画像が表示されました。

これでは画像が保存されません。
そのため拾ってきた画像をNCMBに保存し、アップロードする必要があります。。

NCMBに画像をアップロード

その前にiPhoneで取ってきた画像は大きすぎるのでリサイズする必要があります。
そのために今回は簡単なライブラリを紹介します。
NYXImagesKitです。
これをPodfileに

pod 'NYXImagesKit'

と追加し、pod installを行なってください。

 func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage

        //NYXImagesKitライブラリでリサイズを行う。
        let resizedImage = selectedImage.scale(byFactor: 0.4)


        picker.dismiss(animated: true, completion: nil)

        //data型に変換
        let data = resizedImage!.pngData()

        //型を変換
        let file = NCMBFile.file(withName: NCMBUser.current()?.objectId,data:data) as! NCMBFile

        file.saveInBackground({ (error) in
            if error != nil{
                print(error)
            }else{
                self.userImageView.image = selectedImage

            }
        }) { (progress) in
            print(progress)
        }

    }

これで以下のようにNCMBに保存されるようになりました。
スクリーンショット 2019-07-07 8.37.32.png

次にアップロードした画像を読み込みます。

ユーザーページに読み込む。

ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        //丸くするコード
        userImageView.layer.cornerRadius = userImageView.bounds.width / 2.0
        userImageView.layer.masksToBounds = true
    }

    override func viewWillAppear(_ animated: Bool) {
        if let user = NCMBUser.current() {

            let file = NCMBFile.file(withName: (user.objectId)!, data: nil) as! NCMBFile

            file.getDataInBackground { (data, error) in
                if error != nil{
                    print(error)
                }else{
                    if let image = UIImage(data: data!){
                        self.userImageView.image = image
                    }

                }
            }
        }else{
            // NCMBUser.current()がnilだったとき
            let storyboard = UIStoryboard(name: "SignIn", bundle: Bundle.main)
            let rootViewController = storyboard.instantiateViewController(withIdentifier: "SignInController")
            UIApplication.shared.keyWindow?.rootViewController = rootViewController

            // ログイン状態の保持
            let ud = UserDefaults.standard
            ud.set(false, forKey: "isLogin")
            ud.synchronize()
        }

    }

スクリーンショット 2019-07-07 9.30.50.png

また、このような画像をImageViewのImageに設定しておけば、画像が読み込まれる前に表示することができます。
以上で完成です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】 UITextFieldに文字数制限を設ける方法

前置き

少し久しぶりのQiita投稿。
(自作アプリ公開に向けて日々いろいろやってたんですが、細かいトコの修正やら稼動確認してると、今までの反復に当たる部分でぶつかる壁が多くて、あまり目新しい収穫がなかったので。)

さて表題の件。
ブログ等を参考にしても、エントリ時期から時間が経ってSwiftの書き方にギャップが出たり、(日本語入力もカバーするか、という点で)実装の仕方に違いがあったりで、マネしてみても自分の想定する実装にならないコトが多い。
最終的に、いささか記事のエントリ時期としては古くて、少しSwift5に対応するために修正は必要だったけれど、下記「参考」記載のURLのブログエントリが自分なりに一番取り込みやすい内容でした。

開発環境

端末:MacBook Pro/MacOS 10.14.5(Mojave)
Xcode:10.2.1
Swift:5

実装内容

「こういうコトやりたい」だけ前置きに書いていても、
実際、下記のソースで何ができるようになったかが伝わりづらいかもしれないので、
どんなコトがどんな風にできるのか、できるだけ書くようにしようかな、と。
(今回は画面イメージもないので)

・UITextFieldに対する、入力文字数の制限。
・日本語入力(入力→変換→確定、というプロセス)でもOK。
 入力確定時に制限文字数以上であれば切り捨てる。
・あくまで制限文字数のある項目1つに対する入力制限の実装。
 (複数の項目で、それぞれ制限文字数が異なるような場合、別途カスタマイズは必要。)

ソースサンプル

ViewController.swift
class ViewController: UIViewController, UITextFieldDelegate {

    // 入力チェックを行うUITextField
    @IBOutlet weak var myTextField: UITextField!
    // 入力可能な最大文字数
    let maxLength: Int = 5

    override func viewDidLoad() {
        super.viewDidLoad()

        myTextField.delegate = self        

        // myTextFieldの入力チェック(文字数チェック)をオブザーバ登録
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(textFieldDidChange(notification:)),
                                               name: UITextField.textDidChangeNotification,
                                               object: myTextField)
    }

    // オブザーバの破棄
    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    // 入力チェック(文字数チェック)処理
    @objc func textFieldDidChange(notification: NSNotification) {
        let textField = notification.object as! UITextField

        if let text = textField.text {
            if textField.markedTextRange == nil && text.count > maxLength {
                textField.text = text.prefix(maxLength).description
            }
        }
    }

        ...    
}

参考

・雑食プログラミング備忘録
 [iOS] UITextFieldの最大文字数を設定する
 http://chicketen.blog.jp/archives/50002171.html

感想

会社での担当業務としてHTMLを触るコトが多いのもあって、UITextFieldの文字数制限くらいStoryboardでチャチャっと設定できると思っていたトコロから、気づいたら実装・稼動確認、そしてここへアウトプットするのも合わせるとフツーに2、3時間経ってました。
英語入力だけの文字数制限だけなら割とすぐにできたトコロから、日本語に対応したモノへ発展させるのにけっこう悩まされました。

Swift2,3の頃の記事が多くて、Swift4,5だと書き方が全然違って来てる(もしくは同じ挙動にならない)っていうコトに割と出くわすので、自分自身がSwift5で色々試して、そのギャップを埋めるコトにささやかながら貢献できれば嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む