著者アーカイブ 村上

村上 著者:村上

【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 で読み込み中を表すインジケーターを表示する方法でした。
ご参考になれば幸いです。

村上 著者:村上

【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 ページが表示できない時の対処法でした。
根本的な解決にはなっていませんが…ご参考になれば幸いです。

村上 著者:村上

【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 にしても大丈夫なのでは!?と思い試して見ましたが…こちらの方法では、相変わらずボタンアイコンがグレーになってしまい、ダメでした。
調べてみれば方法はあるのかもしれませんが、現時点で確実なのは上記の方法かと思います。
とりあえず、現在は特に問題はないので、この方法を使用したいと思います。

 

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

村上 著者:村上

【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 送信を行う方法でした。
ご参考になれば幸いです。

村上 著者:村上

【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 追加」などで調べただけだと今回の手法にはなかなかたどり着けなかったので、同じことにお悩みの方のご参考になれば幸いです。

村上 著者:村上

【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 を変更する方法でした。
ご参考になれば幸いです。

村上 著者:村上

【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 のエフェクトも面白かったので、機会があれば是非使ってみたいと思います。
ご参考になれば幸いです。

村上 著者:村上

【Cordova】アプリで表示する画像を「imgcache.js」を使ってキャッシュする

まだ実装途中ですが、備忘録としてまとめ。
「imgcache.js」を使って、Cordova アプリで表示している画像をキャッシュする方法についてです。

なお、使用したライブラリの GitHub はこちらから。

GitHub – chrisben/imgcache.js: JS library based on the File API to cache images for offline recovery (target: cordova/phonegap & chrome)
https://github.com/chrisben/imgcache.js

 

実装方法ですが、まず下記の npm コマンドでライブラリをインストールします。

npm install imgcache.js --save

また、File 関係の Cordova プラグインも必要なので、合わせてインストールします。

cordova plugin add cordova-plugin-file --save
cordova plugin add cordova-plugin-file-transfer --save

なお、私の環境では既に cordova-plugin-file がインストールされていたのですが、他のプラグインの兼ね合いで、バージョンが低いものがインストールされていました。
で、そのままだと cordova-plugin-file-transfer がインストールできなかったので、オプションの --force を付けて強引にインストールしました。

インストールできたら、ライブラリをインポートします。

import ImgCache from 'imgcache.js';

node_modules > imgcache ディレクトリ内にある imgcache.js を任意の場所にコピーし、それをインポートします。

インポートまで完了したら、あとは ImgCache を初期化して実行するだけです!
初期化は下記のサンプルコードの通りです。

ImgCache.options.debug = true;
ImgCache.options.chromeQuota = 50*1024*1024;
ImgCache.options.usePersistentCache = true;
ImgCache.init(function() {
    // 初期化成功
}, function() {
    // 初期化失敗
});

で、初期化が完了したら、下記のコードで画像のキャッシュを行えます。

var target = $('img#cacheImg');
ImgCache.isCached([キャッシュしたい画像のパス], function(path, success) {
  if (success) {
    ImgCache.useCachedFile(target);
  } else {
    ImgCache.cacheFile(path, function() {
      ImgCache.useCachedFile(target);
    });
  }
});

1行目でキャッシュしたい画像が格納される img 要素を指定しています。
なお、こちらは適宜変更してください。
で、2行目で画像がキャッシュ済みかを調べ、キャッシュ済みだったらキャッシュされた画像を表示し、まだキャッシュされていなかったら、画像をキャッシュしてそれを表示しています。

処理としてはこれだけなのですが、私の環境では下記のメッセージが表示されてしまい、画像のキャッシュに失敗しております!

WARN: No source given to getCachedFileName
INFO: File undefined not in cache

どうやら画像の保存場所が悪いらしい…?
ちなみに、実行環境は iOS です。
解決方法がわかったら、また改めてご紹介したいと思います。

 

以上、Cordova アプリで画像をキャッシュする方法でした。
なお、当然ですが、ImgCache の初期化が完了していない状態で、画像をキャッシュしようとしたり、保存している画像を取得しようとしたりすると警告が表示されますので、ご注意ください。
綺麗な方法かはわかりませんが、私は ImgCache.init() が成功したらフラグを立てて、フラグが立っていたら、画像キャッシュの処理などを行うようにしています。

ご参考になれば幸いです。

村上 著者:村上

【Swift】正規表現による文字列の検索を行う方法

以前に、「【Swift】正規表現を使わずに指定した文字列が含まれているかを判定する」という記事を投稿しましたが、今回は正規表現を使って文字列の検索を行う方法についてです。

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

正規表現による文字列の抽出(Swift) [NSRegularExpression, NSTextCheckingResult] iOS Objective-C, Swift Tips-モバイル開発系(K)
http://www.office-matsunaga.biz/ios/description.php?id=28

iOSエンジニアの正規表現入門 – Qiita
https://qiita.com/shoheiyokoyama/items/5dc67fdc9e06a9dc5728

 

サンプルコードは下記の通りです。

let regex = try? NSRegularExpression(pattern: "[正規表現のパターン]", options: NSRegularExpression.Options())
if regex!.firstMatch(in: [検索したい文字列], options: NSRegularExpression.MatchingOptions(), range: NSMakeRange(0, [文字列の長さ(文字数)])) != nil {
    // 一致した場合に行いたい処理を記述
}

正規表現のパターンは、上記で紹介した記事の内、2つ目が参考になります。
ほとんど JavaScriptPHP と同じなので、他の言語で触ったことがある場合は特に困ることはないかと思います。
なお、2行目ので指定している関数の返り値に、検索で一致した文字列の情報が含まれています。
今回は、一致したらどうかの判断だけが行えればよかったので、特に返り値を保持していませんが、必要に応じてご活用ください。

 

以上、Swift で文字列の検索を正規表現を使用して行う方法でした。
ご参考になれば幸いです。

村上 著者:村上

【Swift】「SideMenu」ライブラリを使ってサイドバーメニューをコードのみで実装する

タイトル通り、サイドバーメニューをコードのみで実装する方法です。
Storyboard を使う方法もちらほらありましたが、今後複数人で開発することになった場合に管理が面倒になるのと、個人的に Storyboard が苦手なので使いたくなかったので、コードのみの実装方法を採用しました。

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

ios SideMenu ライブラリの使い方 – とりあえずphpとか
http://kimagureneet.hatenablog.com/entry/2019/09/06/223040

また、使用したライブラリ「SideMenu」の GitHub ページはこちらです。

GitHub – GitHub – jonkykong/SideMenu: Simple side/slide menu control for iOS, no code necessary! Lots of customization. Add it to your project in 5 minutes or less.
https://github.com/jonkykong/SideMenu

 

早速実装方法ですが、まず Podfile に下記を追加し、pod install コマンドを実行します。

pod 'SideMenu'

そうすると、SideMenu ライブラリが使用可能になります。

次に、任意の名前のコントローラを作成します。
私は MenuViewController.swift という名前で作成しました。
中身は下記の通り。
なお、サイドバーのメニューは TableView を使って表示しています。

import UIKit
import SideMenu

class MenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    private var tableView = UITableView();
    var items: [String] = ["メニュー1","メニュー2","メニュー3"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 背景色は白にする
        view.backgroundColor = .white
        
        // 見た目調整
        self.navigationController?.navigationBar.tintColor = .clear
        self.navigationController?.navigationBar.barStyle = UIBarStyle.black
        self.navigationController?.navigationBar.isTranslucent = true
        self.navigationController?.navigationBar.backgroundColor = .blue
        
        // TableView を追加
        tableView.frame = view.bounds
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.tableFooterView = UIView()
        view.addSubview(tableView)
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // セルの選択を解除
        tableView.deselectRow(at: indexPath, animated: true)
        // サイドバーを閉じる
        dismiss(animated: true, completion: nil)
        
        NotificationCenter.default.post(
            name: Notification.Name("SelectMenuNotification"),
            object: nil,
            userInfo: ["itemNo": indexPath.row] // 返したいデータをセットする
        )
    }
}

 

次に、サイドバーメニューを追加したいページに下記を追加します。
なお、import SideMenu の追加を忘れずに。

override func viewDidLoad() {
    super.viewDidLoad()
    
    ......

    // サイドバーメニューからの通知を受け取る
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(catchSelectMenuNotification(notification:)),
        name: Notification.Name("SelectMenuNotification"),
        object: nil
    )
}
// 選択されたサイドバーのアイテムを取得
@objc func catchSelectMenuNotification(notification: Notification) -> Void {
    // メニューからの返り値を取得
    let item = notification.userInfo // 返り値が格納されている変数
    // 実行したい処理を記述する
}

サイドメニューバーを開く時には下記のコードを使います。

let menu = SideMenuManager.default.menuLeftNavigationController!
present(menu, animated: true, completion: nil)

実装に必要なコードは以上です。
なお、今回はサイドバーのメニューの表示に TableView を使っていますが、もちろんボタンとかにしても OK です。
その場合は、MenuViewController.swiftviewDidLoad() 内で記述してください。

 

以上、Swift でサイドバーメニューをコードのみで実装する方法でした。
ご参考になれば幸いです。