カテゴリーアーカイブ Swift

村上 著者:村上

【Swift】UITextField入力後に画面をタップしてキーボードを非表示にする

Swift で実装しとくと便利かな?な機能その2。
今回は、文字入力時に表示されたソフトウェアキーボードを、TextField や キーボード以外のばしょをタップして消す方法です。
確か Android は特に指定しなくても入力欄外をタップすればキーボードは非表示になったと思うのですが…iOS はわざわざ実装しないと消えないんですね…。

今回参考にさせていただいたサイトはこちらから。

[Swift] resignFirstResponder 以外でキーボードを隠す – Qiita
https://qiita.com/takecian/items/ebcdd3cc37c90a054020

 

実装方法は下記の通りです。
画面がタップされた時に呼ばれる touchesBegan をオーバーライドして、その中にキーボードを閉じる処理を追加します

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    // キーボードを閉じる
    view.endEditing(true)
}

他にも、UITextFieldresignFirstResponder メソッドを使う方法もありますが、こちらの方法だと、TextField が複数あった場合、全ての TextField でメソッドを実行しなければいけないので面倒です。
かく言う私も、最初は resignFirstResponder を使っていたのですが、endEditing を知り、速攻でこちらに書き換えました。
アプリの挙動にも全く問題ないので、よほどのことがない限りは、view.endEditing(true) を使った方が簡単だと思います!

 

以上、UITextField 入力後に、入力欄外やキーボード以外の画面をタップしてソフトウェアキーボードを非表示にする方法でした。
ご参考になれば幸いです。

なお、Swift で実装しとくと便利かな?な機能その1 はこちらです。

【Swift】UITextField入力後にキーボードのリターンキーで次のUITextFieldに移動する
https://cpoint-lab.co.jp/article/201911/12629/

必須ではないですが、こちらもあると便利かもしれません。

  • この記事いいね! (0)
村上 著者:村上

【Swift】UITextField入力後にキーボードのリターンキーで次のUITextFieldに移動する

実装しとくと便利かな?な機能その1。
今回は、TextField にテキストなどを入力後、ソフトウェアキーボードのリターンキーで次の TextField に移動する方法です。
ログインや会員登録などのフォーム画面に実装されていることが多いと思います。

今回参考にさせていただサイトはこちら。

UITextFieldのキーボード操作で使えるTips – Goalist Developers Blog
http://developers.goalist.co.jp/entry/2017/03/16/190535

 

サンプルコードはこちら。
なお、今回 Storyboard は使用せず、全てコードのみで実装しています。

まずは、テキスト入力欄を作成します。
今回は、ログインフォームをイメージして、名前とパスワードの入力欄を追加しました。

// 名前の入力欄を作成
var name: UITextField = UITextField(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
name.delegate = self
name.layer.position = CGPoint(x: self.view.bounds.width/2, y: 200)
name.returnKeyType = .next
name.tag = 0
self.view.addSubview(name)

// パスワードの入力欄を作成
var password: UITextField = UITextField(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
password.delegate = self
password.layer.position = CGPoint(x: self.view.bounds.width/2, y: 300)
password.isSecureTextEntry = true
password.returnKeyType = .Done
password.tag = 1
self.view.addSubview(password)

ポイントは、4行目と12行目の .returnKeyType と、5行目と13行目の .tag です。
まず、.returnKeyType でソフトウェアキーボードのリターンキーを「Next」と「Done」の文字に変更しています。

次に、.tag で それぞれの UITextField のタグを指定しています。
なお .tag には数値しか指定できないのでご注意ください。

次に、リターンキーを押した時の処理を追加します。

// リターンキーを押した時の処理
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    switch textField.tag {
    case 0:
        // タグが 0 なら password にフォーカスを当てる
        password.becomeFirstResponder()
        break
    case 1:
        // タグが 1 ならキーボードを閉じる
        password.resignFirstResponder()
        break
    default:
        break
    }
    return true
}

コメントに書いてある通りですが、UITextField のタグが 0、つまり名前入力欄だったら、リターンキーを押した時にパスワード入力欄にフォーカスを当てます。
次に、UITextField のタグが 1、つまりパスワード入力欄だったら、ソフトウェアキーボードを閉じます。

実装するコードは以上です。

 

以上、UITextField にテキストを入力した後、キーボードのリターンキーで次の UITextField に移動する方法でした。

なお、これらの処理を簡単に実装できる FormChangeable というライブラリもありましたが、私の環境ではエラーが発生してしまったので、今回は採用しませんでした。
個人的な感覚では、ライブラリを使わなくてもそこまで複雑な処理ではないので、自前で実装でも問題ないと思います。

  • この記事いいね! (0)
村上 著者:村上

【Swift】「A background URLSession with identifier [identifier名] already exists!」警告の対処法

Swift の URLSesssion を使った POST 送信の処理を実装した際に発生した警告についての対処法です。
これのせいでアプリがクラッシュするということはないのですが、私の環境では dismiss() が正常に作動せず、前のページに戻れないというバグが発生したので、修正することをお勧めします。

今回参考にさせていただいた記事はこちら。

【Swift】URLSessionまとめ – Qiita
https://qiita.com/shiz/items/09523baf7d1cd37f6dee#1アプリ1URLSession

 

こちらの記事内で、下記のような記述があります。

・同時平行処理される複数のURLSessionTaskは1つのURLSessionで共有可能。
・独自でURLSessionを作成している場合は、finishTasksAndInvalidateやinvalidateAndCancelでデリゲートやコールバックオブジェクトへの参照をクリアしないとメモリーリークを起こす。

で、自分のプロジェクトを確認したところ…セッションを無効にする処理を追加してない!
ということで、下記のコードを追加して修正しました。

// ページが破棄される直前に呼び出される
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // 通信キャンセル&セッション無効
    self.session?.invalidateAndCancel()
}

私の場合は、ページが破棄される直前に Session も無効にしていますが、他のタイミングで実行しても問題ありません。
適宜、環境に合わせて変更してください。

また、URLSession は下記のようにグローバル変数で宣言することをお忘れなく。

var session: URLSession?

URLSession を使った POST 送信処理については、以前に記事を投稿しましたので、そちらを参考にして頂けると嬉しいです。

【Swift】URLSessionを使ってPOST送信を行う方法
https://cpoint-lab.co.jp/article/201911/12524/

 

以上、Swift の URLSession を使用した際に、「A background URLSession with identifier [identifier名] already exists!」という警告が発生する際の対処方法についてでした。
ご参考になれば幸いです。

  • この記事いいね! (0)
村上 著者:村上

【Swift】読み込み中を表すインジゲーターを表示する方法

タイトル通り、iOS アプリにインジゲーターを表示する方法です。
予想以上に簡単だったので、備忘録兼、今後のコピペ元にするためにまとめ。

参考にさせていただいた記事はこちら。

【Swift】UIActivityIndicatorViewを使う【ぐるぐる】 – Qiita
https://qiita.com/wai21/items/aa105f167bc94a94fe51

 

まず、viewDidLoad() 内に下記を追加し、インジケーターの位置や色などの設定を行います。

// インジゲーターの設定
var indicator = UIActivityIndicatorView()
// 表示位置を設定(画面中央)
indicator.center = view.center
// インジケーターのスタイルを指定(白色&大きいサイズ)
indicator.style = .whiteLarge
// インジケーターの色を設定(青色)
indicator.color = UIColor(red: 44/255, green: 169/255, blue: 225/255, alpha: 1)
// インジケーターを View に追加
view.addSubview(indicator)

今回は、標準サイズよりも大きめのサイズで、色は iOS で使われている青色にしています。
基本的な設定は上記だけで OK です。
なお、.color については .style の後に記述しないと無効になってしまうそうなのでご注意ください。

が、これだけだと表示されないので、インジケーターを表示したいタイミングで下記を呼び出します。

// インジケーターを表示&アニメーション開始
indicator.startAnimating()

で、終了したいタイミングで下記を呼び出します。

// インジケーターを非表示&アニメーション終了
indicator.stopAnimating()

実装についてはこれだけです!

なお、インジケーターの表示/非表示はメインスレッドで行う必要があるので、状況に合わせて下記のように記述するようにしてください。

DispatchQueue.main.async {
    // メインスレッドでインジケーターを停止する
    indicator.stopAnimating()
}

他にも、.isAnimating プロパティでアニメーションがされているかを取得したり、.hidesWhenStopped でアニメーションしていない時に非表示にするかどうかを指定できます。
こちらも環境に合わせて指定してください。

 

以上、Swift で読み込み中を表すインジケーターを表示する方法でした。
ご参考になれば幸いです。

  • この記事いいね! (0)
村上 著者:村上

【Swift】「SFSafariViewController」でWebページが表示されない時の対処法

iOS アプリで Webページを表示させたくて SFSafariViewController を使用していたのですが、開こうとしても白紙のページが表示されるだけという現象が発生しました。
1回目は成功するのですが、一度ページを閉じた後、再度開くと白紙になる…という状況です。
時間を開ければ表示されたりもするのですが、それだと使えないので対処法を調査しました。

 

さて対処法ですが、結局 SFSafariViewController をやめて WKWebView を使うのが一番手っ取り早いようでしたので、そちらの方法を採用することにしました。
ということで、実装のサンプルコードです。

まず、遷移先の WebView ページのコードがこちら。
地図を表示させているので、MapController.swift という名前になっています。
こちらは適宜変更してください。

import UIKit
import WebKit

class MapController: UIViewController, WKNavigationDelegate, WKUIDelegate {
    var webView: WKWebView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "マップ"
        let rightReloadButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(reloadPage(_:)))
        self.navigationItem.setRightBarButtonItems([rightReloadButton], animated: true)
        // WebView の設定
        webView = WKWebView(frame:CGRect(x:0, y:0, width:self.view.bounds.size.width, height:self.view.bounds.size.height))
        webView?.uiDelegate = self
        webView?.navigationDelegate = self
        self.view.addSubview(webView!)

        // URL設定
        let urlString = [表示したいWebページのURL]
        let url = URL(string: urlString)
        let request = URLRequest(url: url!)
        webView!.load(request)
    }
    
    // リロード処理
    @objc func reloadPage(_ sender: UIButton) {
        if webView?.url != nil {
            webView?.reload()
        }
    }
}

ナビゲーションバーの右端にはリロードボタンも追加しています。
こちらは必要がなければ削除してください。

そして、上記のページを開く時のコードはこちら

// 遷移するViewを定義する.
let mapController: MapController = MapController()
// View を移動する.
self.navigationController?.pushViewController(mapController, animated: true)

コメントを抜かせば、たった 2行でOKです。

 

もちろん、SFSafariViewController を使用すれば MapController.swift を用意しなくて済むのでコード行数も少なくて済むのですが、原因のわからないエラーが発生したので作り直してしまった方が簡単でした。
しかし、別の問題もあり、WebView を開いた数十秒後に下記のような警告が発生しました…。

Could not signal service com.apple.WebKit.Networking: 113: Could not find specified service

警告なので無視しても大丈夫そうではありますが…ちょっと気になるので、調べて見てすぐ解決できそうなら対応したいと思います。

 

以上、Swift の SFSafariViewController で Web ページが表示できない時の対処法でした。
根本的な解決にはなっていませんが…ご参考になれば幸いです。

  • この記事いいね! (0)
村上 著者:村上

【Swift】NavigationBarのボタンアイコンがグレーになる時の対処法

再現するのがかなり限定的な時なのですが、私は地味に困ったので備忘録としてまとめ。
タイトル通り、ナビゲーションバーのボタンアイコンが、特に何も指定していないのにグレーになってしまう時の対処法です。
起動時にいきなりそうなるのではなく、別のページへ遷移した後に元のページに戻ってくると発生していました。
別にボタン自体はタップできるので、動作に問題はなかったのですが…やはり見た目が気になるのでどうにかしたい!

 

で、いろいろ確認したところ、アラートとページ遷移時のアニメーションが原因でした。
アプリで「接続中です…」というメッセージをアラートで表示し、画面遷移するときに消すという処理を実装しているのですが、ページ遷移の際にオプションの animatedfalse にしていたのが悪かったようです。
具体的に書くと下記の通りです。

// View を移動する.
self.navigationController?.pushViewController([遷移先のViewController], animated: false)  // 画面遷移時のアニメーションを無効にする

こちらのオプションを false にすると、画面遷移時のアニメーションが無効になるのですが、その場合、アラートが正しく閉じられていないようで…そのせいで、ナビゲーションバーのボタンのアイコンがグレーになってしまっていたようです。
多分ですが、アラートが表示されている間に表示されるグレーの透明背景の影響で他のボタンがグレーになっていて、それが残ってしまっているのだと思います。

なので、ちゃんとアラートを閉じてからページ遷移するサンプルコードはこちら。

// アラートを閉じる
alert?.dismiss(animated: true, completion: nil)
// View を移動する.
self.navigationController?.pushViewController([遷移先のViewController], animated: true)

ちなみに、アラートを閉じる時のオプションの animatedfalse にすれば、ページ遷移時の animatedfalse にしても大丈夫なのでは!?と思い試して見ましたが…こちらの方法では、相変わらずボタンアイコンがグレーになってしまい、ダメでした。
調べてみれば方法はあるのかもしれませんが、現時点で確実なのは上記の方法かと思います。
とりあえず、現在は特に問題はないので、この方法を使用したいと思います。

 

以上、ナビゲーションバーのボタンアイコンがグレーになってしまう時の対処法でした。
だいぶ限定的なので、他に活用できるかは分かりませんが…ご参考になれば幸いです。

  • この記事いいね! (0)
村上 著者:村上

【Swift】URLSessionを使ってPOST送信を行う方法

ちょっと手こずったので、備忘録とコピペ元も兼ねてまとめ。
URLSession を使って Swift でPOST 送信を行う方法についてです。
こちらはログインページとかで使うことが多いと思います。

007 POSTを送信する・GitBook
http://docs.fabo.io/swift/connection/007_post.html

 

いきなり全コードを載せます。
ほぼ上記の参考サイトのコードをコピペしておりますので、そちらをご覧いただいてもOKです。
なお、ViewControllerURLSessionDelegateURLSessionDataDelegate を追加するのを忘れないようにしてください。

// 通信用のConfigを生成.
let config:URLSessionConfiguration = URLSessionConfiguration.background(withIdentifier: "login")
// Sessionを生成.
let session: URLSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
// 通信先のURLを生成.
let myUrl:URL = URL(string: "[POST先のURL]")!
// POST用のリクエストを生成.
var myRequest:URLRequest = URLRequest(url: myUrl)
myRequest.httpMethod = "POST"
// 送信するデータを生成、リクエストにセット.
let str: NSString = "username=\(username)&password=\(password)" as NSString
let myData: NSData = str.data(using: String.Encoding.utf8.rawValue)! as NSData
myRequest.httpBody = myData as Data

// タスクの生成.
let task: URLSessionDataTask = session.dataTask(with: myRequest as URLRequest)
// タスクの実行.
task.resume()

なおこちらはログイン時に使っている処理です。
各行で行なっているのはコメントに書いてある通りの処理ですので、それらを追ってもらえば大体内容は分かるかと思います。

個人的に詰まったのは 11行目からの送信するデータのセット部分です。
他に見た参考サイトなどの書き方ではうまくデータが送れず、かつ送信するデータが 1つだけの場合を想定したコードばかりだったので、上のサンプルコードのように、ユーザ名とパスワードなど複数データを送るときはどうしたら!?となっていました。
結論としては、送信したいデータを & で結合すればOKです。

なお、最後の行で task.resume() を行なった後は、下記のデリゲートが呼び出されます。

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    // 帰ってきたデータを文字列に変換.
    do {
        let json: NSDictionary = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSDictionary
        // 後は任意の処理を実行
    } catch {
        print("parse error")
    }
}

私の環境では、POST送信した後に返ってくるデータは JSON だったので、NSDictionary 型の扱いやすい形にパースしています。
あとは、返ってきた値を保存するなど、行いたい処理を実行してください。

また、今回のサンプルコードでは使用していませんが、関数内で session.configuration.identifier を使うと、上のサンプルコードの2行目 URLSessionConfiguration.background で指定した Identifier を取得することができます。
同じページで違う POST 送信をする際でも、送信後に呼び出されるデリゲートは同一なので、こちらを使用して取得できる Identifier で処理の切り分けを行なっています。
なので、ログイン・ログアウトなど POST 送信後の処理が異なる場合は、違う Identifier を指定して、異なる処理を行えるようにしましょう。

 

以上、Swift の URLSession を使って POST 送信を行う方法でした。
ご参考になれば幸いです。

  • この記事いいね! (0)
村上 著者:村上

【Swift】presentで遷移した先のページにナビゲーションバーを表示させる方法

タイトル通り、Swift で作成したアプリで、present を使って遷移した先のページに NavigationBar を追加する方法です。
self.navigationItem.title でタイトルを表示したかったのにできず、地味に困っていたので助かりました。

参考にさせていただいた記事はこちらから。

【Swift3】 presentで画面遷移したときに遷移先のViewにNavigationBarを表示させておく – また1からこつこつと
http://mjk0513.hateblo.jp/entry/2017/03/31/011959

 

コードはこちら。

let nextPageController: NextPageController = NextPageController()
let navigationController = UINavigationController(rootViewController: nextPageController)
self.present(navigationController, animated: true, completion: nil)

ポイントは2行目で、単なるページ遷移だと 1行目で定義したコントローラをそのまま 3行目に渡して遷移させているのですが、上記のように 2行目にナビゲーションコントローラを間に挟むことで、ナビゲーションバーを設定することができます。
変更はこれだけ!
実際に実行したところ、遷移した先のページにナビゲーションバーが追加されていました!

 

以上、Swift で遷移先のページにナビゲーションバーを追加する方法でした。
ずっと遷移先のページで何かしらの設定をするものだと思い込んでいたので、遷移時にこうして設定するとは思い浮かばず…若干ハマりました。
swift navigationbar 追加」などで調べただけだと今回の手法にはなかなかたどり着けなかったので、同じことにお悩みの方のご参考になれば幸いです。

  • この記事いいね! (0)
村上 著者:村上

【Swift】メインスレッド以外で UILabel のテキストを更新する

Swift でアプリを開発している際、HTTP 通信が完了した後など、メインスレッド以外で UI を更新したいという状況になったため、その方法についてまとめ。
本来はメインスレッド以外での UI の更新は非推奨なので、場合によっては挙動が変になることがあるとのこと。
なので、どうしても!という時以外は、メインスレッドで行うようにしましょう。

今回参考にさせていただいた記事はこちらから。

swift – swiftでHTTP通信をした後にUILabelのtextを変更する時に時間がかかる – スタック・オーバーフロー
https://ja.stackoverflow.com/questions/7839/swiftでHTTP通信をした後にUILabelのtextを変更する時に時間がかかる

 

実装方法は簡単で、UI の更新処理を下記の記述で囲むだけです。

OperationQueue.main.addOperation({
    // UI の更新処理を記述する
    textLabel.text = "変更後の文字列"
})

私の場合は 3行目のように、この処理の中で UILabel の更新処理を行なっています。
追加する内容としては以上です。
あとは、実際にアプリを実行してみたところ、問題なく UILabel の内容が書き換わることが確認できました!

 

以上、Swift でメインスレッド以外の場所で UI を変更する方法でした。
ご参考になれば幸いです。

  • この記事いいね! (0)
村上 著者:村上

【Swift】テキスト入力欄をカスタマイズできる「TextFieldEffects」

アプリの画面を格好良くして!という要望を受けたので、とりあえず「TextFieldEffects」を導入してみました。
こちらは Swift の UITextField をカスタマイズできるライブラリです。

GitHub のページはこちらから。

GitHub – raulriera/TextFieldEffects: Custom UITextFields effects inspired by Codrops, built using Swift
https://github.com/raulriera/TextFieldEffects

 

導入方法ですが、CocoaPods を使っている場合は、下記を追加して、pod install コマンドを実行します。

use_frameworks!
pod 'TextFieldEffects'

インストールが完了したら、TextFieldEffects をインポートします。

import TextFieldEffects

あとは、サンプルからお好みのデザインを選び、実装します。
私は今回、「Yoshiko」を使用しました。
サンプルコードはこちらです。

let textField = YoshikoTextField(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
textField.placeholderColor = UIColor(red: 0, green: 149/255, blue: 217/255, alpha: 1)
textField.activeBorderColor = UIColor(red: 44/255, green: 169/255, blue: 225/255, alpha: 1)
textField.inactiveBorderColor = UIColor(red: 234/255, green: 244/255, blue: 252/255, alpha: 1)
textField.placeholder = "テキストを入力してください"
self.view.addSubview(textField)

入力欄がアクティブの時は青色の枠線で囲み、非アクティブの時は薄い水色で入力欄を塗りつぶしています。
Placeholder も青色を指定しています。

 

しかし、私の環境では「SWIFT_VERSION ‘5.0’ is unsupported, supported versions are: 3.0, 4.0, 4.2. (in target ‘TextFieldEffects’)」というエラーが発生し、そもそも TextFieldEffects のインポートもできませんでした。
なので、Pods の TARGETS から TextFieldEffects を選択し、Build Setting から Swift Language Version を変更する必要があります。

画面のスクリーンショットはこちらです。

また、今回のエラーで参考にした記事はこちら。

react native – SWIFT_VERSION ‘5.0’ is unsupported, supported versions are: 3.0, 4.0, 4.2. (in target ‘SwiftyJSON’) – Stack Overflow
https://stackoverflow.com/questions/55710238/swift-version-5-0-is-unsupported-supported-versions-are-3-0-4-0-4-2-in-t

違うライブラリでの対処方法ですが、原因は同じだったようで、こちらで解決できました!

 

以上、Swift の UITextField をカスタマイズする方法でした。
KaedeTextField のエフェクトも面白かったので、機会があれば是非使ってみたいと思います。
ご参考になれば幸いです。

  • この記事いいね! (0)