今回はSwiftUIでは以下のようなiphoneユーザならよく目にする保存UIをコードを追って作っていきます。
SwiftUIは開発者ライクと言われていますが、深堀して行くとやはり難しいですね
このUI機能の仕様
1.ユーザーが入力できるテキスト部分
2.保存機能
3.読み込みボタン
笑っちゃうくらい単純な機能ですね。
全体のコード
//
// ContentView.swift
// Shared
//
// Created by 市川マサル on 2021/06/12.
//
import SwiftUI
//キーボードを下げる
extension UIApplication {//UIApplicationクラス拡張してキーボードを下げる。
func endEditing() {
sendAction(
#selector(UIResponder.resignFirstResponder),
to:nil,from:nil,for:nil
)
}
}
struct ContentView: View {
@State var theText: String = ""
var body: some View {
NavigationView {
TextEditor(text: $theText)
.lineSpacing(10)
.border(Color.gray)
.padding([.leading,.bottom,.trailing])
.navigationTitle("メモ")
.toolbar{
//読み込みボタン
ToolbarItem(placement: .navigationBarTrailing) {
Button {
if let data = loadText("sample.txt") {
theText = data
}
} label: {
Text("読み込み")
}
}
//保存ボタン
ToolbarItem(placement: .navigationBarTrailing) {
Button {
UIApplication.shared.endEditing()//キーボードを下げる
saveText(theText, "sample.txt")//ユーザー定義関数のsaveText()テキストデータを保存する
} label: {
Text("保存")
}
}
}
}
}
func saveText(_ textData:String, _ fileName:String){
//docURLはユーザー定義変数
guard let url = docURL(fileName) else {
return
}
//ファイルパスへの保存
do {
let path = url.path
try textData.write(toFile: path, atomically: true, encoding: .utf8)
} catch let error as NSError {
print(error)
}
}
func docURL(_ fileName:String) -> URL? {
let fileManager = FileManager.default
do {
//Documentsフォルダ
let docsUrl = try fileManager.url(
//Documentフォルダを指定する。
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
let url = docsUrl.appendingPathComponent(fileName)
return url
}catch {
return nil
}
}
//テキストデータを読み込んで返す。
func loadText(_ fileName:String) -> String? {
//URLを得られたらアンラップしてurlに代入
guard let url = docURL(fileName) else {
return nil
}
//urlからの読み込み
do {
let textData = try String(contentsOf: url, encoding: .utf8)
return textData
} catch {
return nil
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ひとつづつ説明していきます。
1.キーボードを下げるコード
appleから提供している便利なコードですこれを実装して呼び出すだけで下げてくれます。大変便利ですし汎用性も高いんではないでしょうか??
呼び出し先としてはテキストの.toolbar内に記述します。toolbarの保存ボタンを押すと閉じる処理と考えるとわかりやすいです。
extension UIApplication {//UIApplicationクラス拡張してキーボードを下げる。
func endEditing() {
sendAction(
#selector(UIResponder.resignFirstResponder),
to:nil,from:nil,for:nil
)
}
}
2.メインビジュアルのコード
struct ContentView: View {
@State var theText: String = ""
var body: some View {
NavigationView {
TextEditor(text: $theText)
.lineSpacing(10)
.border(Color.gray)
.padding([.leading,.bottom,.trailing])
.navigationTitle("メモ")
.toolbar{
//保存ボタン
ToolbarItem(placement: .navigationBarTrailing) {
Button {
UIApplication.shared.endEditing()//キーボードを下げる
saveText(theText, "sample.txt")//ユーザー定義関数のsaveText()テキストデータを保存する
} label: {
Text("保存")
}
}
}
}
}
2-1.ユーザー入力の値を保存する変数
@Stateをつけて変更可能な変数にする。@Stateをつけて宣言された変数は値の更新が見張られるようになり、値が変更したならば値を反映させるためにViewも更新される。
ユーザーの目線からも動向を追跡できる変数宣言といったところだろうか??
今回は言及しません。
@State var theText: String = ""
2-2NavigtionViewでデザイン調整
TextEditorの引数に$theTextを渡すことでユーザーの入力した文字が表示されます。
細かいレイアウトを引数の後に記述しています。
.toolbarで保存ボタンの詳細なデザインや挙動を記載しています。
NavigationView {
TextEditor(text: $theText)
.lineSpacing(10)
.border(Color.gray)
.padding([.leading,.bottom,.trailing])
.navigationTitle("メモ")
.toolbar{
//保存ボタン
ToolbarItem(placement: .navigationBarTrailing) {
Button {
UIApplication.shared.endEditing()//キーボードを下げる
saveText(theText, "sample.txt")//ユーザー定義関数のsaveText()テキストデータを保存する
} label: {
Text("保存")
}
}
}
2-3保存ボタンの設定とテキスト保存のコード
保存ボタン設定しています。
ボタンを配置するツールバーはTexteddditorにモディファイアのtoolbarを追加して作ります。ツールバーにボタンを追加するには、toolbarのコンテンツとしてToolbarItem()の引数placementを指定するとツールバーの右端に寄った位置に表示されます。
先ほど記述したUIApllicationを呼び出し、endEditingを実行します。
saveText(theText,"sample.txt")ユーザー定義関数を呼び出し、メモを保存します。
.toolbar{
//保存ボタン
ToolbarItem(placement: .navigationBarTrailing) {
Button {
UIApplication.shared.endEditing()//キーボードを下げる
saveText(theText, "sample.txt")//ユーザー定義関数のsaveText()テキストデータを保存する
} label: {
Text("保存")
}
}
}
3.saveText()定義:テキストファイルへの保存を例外処理の中で行う。
.toolbarモディファイア内に記述したユーザー定義関数です。呼び出し側ではsaveText(ユーザーの入力値が入った変数,"ファイル名")でテキストデータを保存しています。
saveTextの第一引数でユーザーの入力値を受け取り、第二引数fileNameでファイル名を受け取ります。この例ではsample.txtという名前で受け取りますが、これだけでは保存する場所が決まりません。そこでユーザー定義関数docURL(filename)を実行して保存するURlを取得して、そのURLのパス(url.path)に書き込みます。なんらかの理由でnilになった時のためにguard let-elseを使います。
3-1.aveTextユーザー定義関数
func saveText(_ textData:String, _ fileName:String){
//docURLはユーザー定義変数
guard let url = docURL(fileName) else {
return
}
//ファイルパスへの保存
do {
let path = url.path
try textData.write(toFile: path, atomically: true, encoding: .utf8)
} catch let error as NSError {
print(error)
}
}
3-2.docURLユーザー定義関数
第一引数で(filename)を受け取ります。返り値はURL?で設定します。ファイルはiphoneデバイス内のどこでも保存できるというものではなく、目的に応じて決まっています。ユーザーが作って読み書きするファイルはDocumentsフォルダ内に保存します。DocumentsフォルダまでのURLはFileManager.defaultでfileManagerオブジェクトを作りfileManager.url()で取得する事ができます。DocumentsフォルダまでのURLを取得したならば、appendingPathComponent(fileName)で保存ファイル名を追加したURLを作り、そのURLをdocURL(fileName)の値として返します。
尚、ここでもエラーを返す場合があるのでdo-try-catchの例外処理の中で実行し、エラーになったならURLではなくnilを返します。nilを返す可能性があることから、戻り値の型はURL?のようにOputional型にします。
func docURL(_ fileName:String) -> URL? {
let fileManager = FileManager.default
do {
//Documentsフォルダ
let docsUrl = try fileManager.url(
//Documentフォルダを指定する。
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
let url = docsUrl.appendingPathComponent(fileName)
return url
}catch {
return nil
}
}
4.ツールバーに読み込みボタンを追加する。
読み込みボタンも保存ボタンと同じようにToolbarItemとしてツールバーに追加します。placementで指定する配置する位置は保存ボタンと同じく.navigationBarTralingですが、後から指定したバーアイテムの方が右端に来るので、コード順に従って「読み込み」「保存」の順番で並びます。
読み込みボタンではloadText("sample.txt")を実行してsample.txtからテキストデータを読み込みます。読み込みに失敗した場合はnilが返されるので、if let-elseのオプショナルバインディングを使って読み込んだデータを変数dataに代入し、nilでなければdataをtheTextに代入してテキストエディタに表示します。
loadTextユーザー定義関数については後述します。
4-1読み込みボタン設置
読み込みボタンも保存ボタン同様,ToolbarItemとして.toolbar内に記述します。
.toolbar{
//読み込みボタン
ToolbarItem(placement: .navigationBarTrailing) {
Button {
if let data = loadText("sample.txt") {
theText = data
}
} label: {
Text("読み込み")
}
}
//保存ボタン
ToolbarItem(placement: .navigationBarTrailing) {
~略~
4-2loadText()
読み込みボタンではユーザー定義関数のloadText("sample.txt")でsample.txtからテキストデータを読み込んでいます。saveText()同様、docURL(fileName)で読み込むファイルのURLを作り、String(contentsOf: url,encoding:.utf8)でurlからテキストデータを読み込んでいます。saaveText同様、do-try-catchの例外処理の中で実行します。
もしエラーになったならば関数の値としてnilを戻すので,戻り値の型はString?のようにoptional型にします。
func loadText(_ fileName:String) -> String? {
//URLを得られたらアンラップしてurlに代入
guard let url = docURL(fileName) else {
return nil
}
//urlからの読み込み
do {
let textData = try String(contentsOf: url, encoding: .utf8)
return textData
} catch {
return nil
}
}
メモ帳保存機能、読み込み機能、書き込み機能に必要なものはURLを作るdocURL(),保存のsaveText(),読み込みのloadText(),キーボードを下げるextension UIApplication,テキストエディタ,保存ボタン,読み込みボタン,を作るstract ContentView,そして最後にプレビュー画面を作るstructContentView_previewsがあります。
以上。
