
【じっくりSw1ftUI46】実践編16〜第28章 AppStorage と SceneStorage を使用した SwiftUI データの永続化
さてと、前回
で
Environmentプロパティ
の簡単な使い方の紹介はやったので〜〜〜〜今回は、
テキストフィールドなんかに入力した値を永続化させる初歩
👉データ管理の入り口
に入ってく🕺
毎度のことだが、オイラの学びなんか要らないって人は、
でまんまサンプルコードも載ってそうだから好きにしたらいいんじゃね👀💦
さてと、んだば早速
じっくり第28章を読んでく👓
概要
@AppStorage
@SceneStorage
てゆー
プロパティラッパー
を使えってゆーてるみたいだね。
*プロパティラッパーについて、振り返りたいって人は
ですでに記事にしてるからそっちも参照してや〜〜〜
@SceneStorage
うだうだ書くよりも動かした方が早いので、
import SwiftUI
struct Essentials28: View {
var body: some View {
Essentials28ContentsView()
}
}
struct Essentials28ContentsView: View {
//@SceneStorage
@SceneStorage("menu") var menu:String = ""
//@AppStorage
@AppStorage("fruits") var fruits: String = ""
var body: some View {
ScrollView{
VStack{
TextField("",text: $menu)
.background(Color.orange)
}
}
}
}
#Preview {
Essentials28ContentsView()
}
てな感じのコードだと、AppStorageがUserDefaultに追加されるだけみたいなんで〜〜〜書かれているとおりにApp Groupsを

import SwiftUI
struct Essentials28: View {
var body: some View {
Essentials28ContentsView()
}
}
struct Essentials28ContentsView: View {
//@SceneStorage
@SceneStorage("menu") var menu:String = ""
//@AppStorage
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) var fruits: String = ""
var body: some View {
ScrollView{
VStack{
TextField("項目を入力してください",text: $menu)
TextField("果物名を入力してください",text: $fruits)
}
}
}
}
#Preview {
Essentials28ContentsView()
}
てな感じのコードで動かすと、




AppGroupsにはめ込んだAppStorageの物しか残ってないね👀
面白いのでもう一個増やして〜〜〜
import SwiftUI
struct Essentials28: View {
var body: some View {
Essentials28ContentsView()
}
}
struct Essentials28ContentsView: View {
//@SceneStorage
@SceneStorage("menu") var menu:String = ""
//@AppStorage:App Groups
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) var fruits: String = ""
//@AppStorage:UserDafaults
@AppStorage("basket") var basket: String = ""
var body: some View {
ScrollView{
VStack{
TextField("項目を入力してください",text: $menu)
TextField("果物名を入力してください",text: $fruits)
TextField("かごを入力してください",text: $basket)
}
}
}
}
#Preview {
Essentials28ContentsView()
}
動かすと

プレビューを再表示しても〜〜〜

ここでポイント①:プレビューでも永続的にデータが保存されてるか
を確認したいなら、
@AppStorageを使ってUserDafaultsかApp Groupsに格納した方がいい
ってのが分かるね。
オイラの個人的な経験と感想だけど、
安全に丁寧に仕事をする職人さんで、ビュー単体の確認もせずに、いきなりシミュレータで実行して初めてデータが格納されてるって人を見たことも聞いたこともないので、
少なくともAppStorageでやった方が安全
ってのは分かるよね🧐
で次から
Storgeのデモに入ろう
って感じなんで〜〜〜
import SwiftUI
struct Essentials28ContentsView: View {
var body: some View {
VStack{
TabView{
Essentials28StandardView()
.tabItem {
Image(systemName: "sun.max")
Text("基本")
}
E28SceneStorageView()
.tabItem {
Image(systemName: "cloud")
Text("SceneStorage")
}
E28AppStorageAppGroupsView()
.tabItem {
Image(systemName: "cloud.rain")
Text("AppGroups")
}
E28AppStorageUserDefaultsView()
.tabItem {
Image(systemName: "apple.logo")
Text("UserDefaults")
}
}
}
}
}
struct Essentials28StandardView: View {
//@SceneStorage
@SceneStorage("menu") var menu:String = ""
//@AppStorage:App Groups
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) var fruits: String = ""
//@AppStorage:UserDafaults
@AppStorage("basket") var basket: String = ""
var body: some View {
ScrollView{
VStack{
TextField("項目を入力してください",text: $menu)
TextField("果物名を入力してください",text: $fruits)
TextField("かごを入力してください",text: $basket)
}
}
}
}
struct E28SceneStorageView: View {
@SceneStorage("menu") private var menu: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $menu)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageAppGroupsView: View {
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) private var fruits: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $fruits)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageUserDefaultsView: View {
@AppStorage("basket") private var basket: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $basket)
.padding(35)
.font(.largeTitle)
}
}
}
#Preview {
Essentials28ContentsView()
}
てな感じでさらに書き換えて動かすと〜〜〜




前回までにやった他のビューに移動=このビューを一旦廃棄
して、再表示させると、


りんごはどこに消えた、、、
🐭🧀🤣
で他も動かしてみると〜〜〜

👉AppGroupsで永続データとして管理してるものを呼び出してるだけ
=当たり前😆






ここからは話の毛色が変わってくるので〜〜〜
ここまでのコード
import SwiftUI
struct Essentials28ContentsView: View {
var body: some View {
VStack{
TabView{
Essentials28StandardView()
.tabItem {
Image(systemName: "sun.max")
Text("基本")
}
E28SceneStorageView()
.tabItem {
Image(systemName: "cloud")
Text("SceneStorage")
}
E28AppStorageAppGroupsView()
.tabItem {
Image(systemName: "cloud.rain")
Text("AppGroups")
}
E28AppStorageUserDefaultsView()
.tabItem {
Image(systemName: "apple.logo")
Text("UserDefaults")
}
}
}
}
}
struct Essentials28StandardView: View {
//@SceneStorage
@SceneStorage("menu") var menu:String = ""
//@AppStorage:App Groups
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) var fruits: String = ""
//@AppStorage:UserDafaults
@AppStorage("basket") var basket: String = ""
var body: some View {
ScrollView{
VStack{
TextField("項目を入力してください",text: $menu)
TextField("果物名を入力してください",text: $fruits)
TextField("かごを入力してください",text: $basket)
}
}
}
}
struct E28SceneStorageView: View {
@SceneStorage("menu") private var menu: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $menu)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageAppGroupsView: View {
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) private var fruits: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $fruits)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageUserDefaultsView: View {
@AppStorage("basket") private var basket: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $basket)
.padding(35)
.font(.largeTitle)
}
}
}
#Preview {
Essentials28ContentsView()
}
カスタマイズされたデータの格納
と、ここでなんか物凄い大事なこと書いてんね🤣
@AppStorage および @SceneStorageに保存できるのは、特定の型の値のみ
具体的には、以下の6つ
Bool
Int
Double
String
URL
Data
んで、他のデータ型を使いたい時なんだけど
なぜか知らないけど、ここも他の技術書のご多分に漏れず、
分かってる前提で全体コードを端折り過ぎてるだけのサンプル
になっているので、
何をしたいかの概念は分かってもどこに書き込めばいいかが見えないソースコードになってんね👀💦
例えば、構造体で定義した値を取り扱うとき
struct CustomerInfo {
var CustomerFirstName: String
var CustomerLastName: String
}
て感じで


まずは、
struct CustomerInfo: Encodable,Decodable {
var CustomerFirstName: String
var CustomerLastName: String
}
でエンコードとデコードのプロトコルを構造体にセットして〜〜〜
//カスタムデータ
@AppStorage("customInfo") var customerData = Data()
で書き換えてあげて

で、まだはっきしゆーて具体的には名前を呼び出しすらできてないので〜〜〜
のコードを実際にはめ込んでまずは検証


ま、ここまではさっきと同様にエラーが起きないね




エンコード対象のusernameって、
AppStorageのキーワードだから他の変数名じゃないとおかしくないか?
誤ってそうじゃね?👀💦

そもそもおかしなところを直しても、てな感じにになってっし
念の為、場所的におかしいのは承知でif文以降を全て、

つまり、このコード自体が、
きちんと動作検証もせずに、動かしてる
iOS16以前のサンプルコードをそのまま流用してるだけ
👉使い物にならない
ってのが分かったねえ🧐
なので〜〜〜
なんかを参考に〜〜〜
struct E28CustomInfo: Codable, Identifiable {
var id = UUID() // 一意の識別子を提供
let customerName: String //顧客名
let customerNumber: Int //顧客番号
}
struct E28CustomeDataView: View {
private let arrayKey = "顧客情報保存"
@State private var savedCustomer: [E28CustomInfo] = []
@State private var customerName: String = ""
@State private var customerNo: String = ""
init() {
_savedCustomer = State(initialValue: loadCustomerInfo())
}
var body: some View {
VStack {
HStack {
TextField("顧客No", text: $customerNo)
.padding()
.keyboardType(.numberPad)
TextField("顧客名", text: $customerName)
.padding()
}
Button("顧客情報追加") {
addCustomer()
}
.padding()
List {
ForEach(savedCustomer) { customer in
VStack(alignment: .leading) {
Text("顧客番号:\(customer.customerNumber)")
Text(customer.customerName)
.foregroundColor(.gray)
}
}
.onDelete(perform: deleteCustomer)
}
.padding()
Spacer()
}
.padding()
}
private func loadCustomerInfo() -> [E28CustomInfo] {
if let data = UserDefaults.standard.data(forKey: arrayKey),
let products = try? JSONDecoder().decode([E28CustomInfo].self, from: data) {
return products
}
return []
}
private func addCustomer() {
if let number = Int(customerNo) {
let newCustomer = E28CustomInfo(customerName: customerName, customerNumber: number)
savedCustomer.append(newCustomer)
saveCustomer()
customerName = ""
customerNo = ""
}
}
private func deleteCustomer(at offsets: IndexSet) {
savedCustomer.remove(atOffsets: offsets)
saveCustomer()
}
private func saveCustomer() {
if let encodedData = try? JSONEncoder().encode(savedCustomer) {
UserDefaults.standard.set(encodedData, forKey: arrayKey)
}
}
}
で、エンコード、デコードもしっかり入った上で。。。

顧客番号と顧客名を入力してみよう


ちょっと色々気に入らないので〜〜〜
struct E28CustomInfo: Codable, Identifiable {
var id = UUID() // 一意の識別子を提供
let customerName: String //顧客名
let customerNumber: Int //顧客番号
}
struct E28CustomeDataView: View {
private let arrayKey = "顧客情報保存"
@State private var savedCustomer: [E28CustomInfo] = []
@State private var customerName: String = ""
@State private var customerNo: String = ""
init() {
_savedCustomer = State(initialValue: loadCustomerInfo())
}
var body: some View {
VStack {
HStack {
TextField("ID", text: $customerNo)
.padding()
.keyboardType(.numberPad)
TextField("なまえ", text: $customerName)
.padding()
}
Button("顧客情報追加") {
addCustomer()
}
.padding()
List {
ForEach(savedCustomer) { customer in
HStack{
Text("No.\(customer.customerNumber):")
.foregroundColor(.gray)
Text(customer.customerName)
}
}
.onDelete(perform: deleteCustomer)
}
.padding()
Spacer()
}
.padding()
}
private func loadCustomerInfo() -> [E28CustomInfo] {
if let data = UserDefaults.standard.data(forKey: arrayKey),
let products = try? JSONDecoder().decode([E28CustomInfo].self, from: data) {
return products
}
return []
}
private func addCustomer() {
if let number = Int(customerNo) {
let newCustomer = E28CustomInfo(customerName: customerName, customerNumber: number)
savedCustomer.append(newCustomer)
saveCustomer()
customerName = ""
customerNo = ""
}
}
private func deleteCustomer(at offsets: IndexSet) {
savedCustomer.remove(atOffsets: offsets)
saveCustomer()
}
private func saveCustomer() {
if let encodedData = try? JSONEncoder().encode(savedCustomer) {
UserDefaults.standard.set(encodedData, forKey: arrayKey)
}
}
}
てな感じにして


出てくるDeleteをタップ

今回のコード(まとめ)
import SwiftUI
struct Essentials28ContentsView: View {
var body: some View {
VStack{
TabView{
Essentials28StandardView()
.tabItem {
Image(systemName: "sun.max")
Text("基本")
}
E28SceneStorageView()
.tabItem {
Image(systemName: "cloud")
Text("SceneStorage")
}
E28AppStorageAppGroupsView()
.tabItem {
Image(systemName: "cloud.rain")
Text("AppGroups")
}
E28AppStorageUserDefaultsView()
.tabItem {
Image(systemName: "apple.logo")
Text("UserDefaults")
}
E28CustomeDataView()
.tabItem {
Image(systemName: "note")
Text("カスタムリスト")
}
}
}
}
}
struct Essentials28StandardView: View {
//@SceneStorage
@SceneStorage("menu") var menu:String = ""
//@AppStorage:App Groups
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) var fruits: String = ""
//@AppStorage:UserDafaults
@AppStorage("basket") var basket: String = ""
var body: some View {
ScrollView{
VStack{
TextField("項目を入力してください",text: $menu)
TextField("果物名を入力してください",text: $fruits)
TextField("かごを入力してください",text: $basket)
}
}
}
}
struct E28SceneStorageView: View {
@SceneStorage("menu") private var menu: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $menu)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageAppGroupsView: View {
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) private var fruits: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $fruits)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageUserDefaultsView: View {
@AppStorage("basket") private var basket: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $basket)
.padding(35)
.font(.largeTitle)
}
}
}
#Preview {
Essentials28ContentsView()
}
struct E28CustomInfo: Codable, Identifiable {
var id = UUID() // 一意の識別子を提供
let customerName: String //顧客名
let customerNumber: Int //顧客番号
}
struct E28CustomeDataView: View {
private let arrayKey = "顧客情報保存"
@State private var savedCustomer: [E28CustomInfo] = []
@State private var customerName: String = ""
@State private var customerNo: String = ""
init() {
_savedCustomer = State(initialValue: loadCustomerInfo())
}
var body: some View {
VStack {
HStack {
TextField("ID", text: $customerNo)
.padding()
.keyboardType(.numberPad)
TextField("なまえ", text: $customerName)
.padding()
}
Button("顧客情報追加") {
addCustomer()
}
.padding()
List {
ForEach(savedCustomer) { customer in
HStack{
Text("No.\(customer.customerNumber):")
.foregroundColor(.gray)
Text(customer.customerName)
}
}
.onDelete(perform: deleteCustomer)
}
.padding()
Spacer()
}
.padding()
}
private func loadCustomerInfo() -> [E28CustomInfo] {
if let data = UserDefaults.standard.data(forKey: arrayKey),
let products = try? JSONDecoder().decode([E28CustomInfo].self, from: data) {
return products
}
return []
}
private func addCustomer() {
if let number = Int(customerNo) {
let newCustomer = E28CustomInfo(customerName: customerName, customerNumber: number)
savedCustomer.append(newCustomer)
saveCustomer()
customerName = ""
customerNo = ""
}
}
private func deleteCustomer(at offsets: IndexSet) {
savedCustomer.remove(atOffsets: offsets)
saveCustomer()
}
private func saveCustomer() {
if let encodedData = try? JSONEncoder().encode(savedCustomer) {
UserDefaults.standard.set(encodedData, forKey: arrayKey)
}
}
}
Apple公式
さてと、次回は
ここ最近のデータとか環境みたいなコードとかビューの裏側の話と打って変わって、ビューのデザインについて入ってく
第29章 SwiftUI スタック配置と配置ガイド
に入ってく。ここ最近やった
プロパティラッパーによるデータのやり取り
入力なんかをしたデータ永続化の手法
がわかっていないと、どんだけビューを綺麗に作っても、操作一回きりで終わってしまうアプリしか作れないからね🧐
じゃ、また次回🕺
記事公開後、
いつもどおり、
でやった操作を〜〜〜




サンプルコード
◾️Essentials28.swift
import SwiftUI
import WebKit
//タイトル
let essentialsChapter28NavigationTitle = "第28章"
let essentialsChapter28Title = "第28章 AppStorageとSceneStorageを使用した SwiftUIデータの永続化"
let essentialsChapter28SubTitle = "第1節 AppStorageとSceneStorageを使用した SwiftUIデータの永続化"
//コード
let codeEssentials28 = """
struct Essentials28ContensView: View {
var body: some View {
VStack{
TabView{
Essentials28StandardView()
.tabItem {
Image(systemName: "sun.max")
Text("基本")
}
E28SceneStorageView()
.tabItem {
Image(systemName: "cloud")
Text("SceneStorage")
}
E28AppStorageAppGroupsView()
.tabItem {
Image(systemName: "cloud.rain")
Text("AppGroups")
}
E28AppStorageUserDefaultsView()
.tabItem {
Image(systemName: "apple.logo")
Text("UserDefaults")
}
E28CustomeDataView()
.tabItem {
Image(systemName: "note")
Text("カスタムリスト")
}
}
}
}
}
struct Essentials28StandardView: View {
//@SceneStorage
@SceneStorage("menu") var menu:String = ""
//@AppStorage:App Groups
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) var fruits: String = ""
//@AppStorage:UserDafaults
@AppStorage("basket") var basket: String = ""
var body: some View {
ScrollView{
VStack{
TextField("項目を入力してください",text: $menu)
TextField("果物名を入力してください",text: $fruits)
TextField("かごを入力してください",text: $basket)
}
}
}
}
struct E28SceneStorageView: View {
@SceneStorage("menu") private var menu: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $menu)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageAppGroupsView: View {
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) private var fruits: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $fruits)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageUserDefaultsView: View {
@AppStorage("basket") private var basket: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $basket)
.padding(35)
.font(.largeTitle)
}
}
}
#Preview {
Essentials28ContensView()
}
struct E28CustomInfo: Codable, Identifiable {
var id = UUID() // 一意の識別子を提供
let customerName: String //顧客名
let customerNumber: Int //顧客番号
}
struct E28CustomeDataView: View {
private let arrayKey = "顧客情報保存"
@State private var savedCustomer: [E28CustomInfo] = []
@State private var customerName: String = ""
@State private var customerNo: String = ""
init() {
_savedCustomer = State(initialValue: loadCustomerInfo())
}
var body: some View {
VStack {
HStack {
TextField("ID", text: $customerNo)
.padding()
.keyboardType(.numberPad)
TextField("なまえ", text: $customerName)
.padding()
}
Button("顧客情報追加") {
addCustomer()
}
.padding()
List {
ForEach(savedCustomer) { customer in
HStack{
Text("No.\\(customer.customerNumber):")
.foregroundColor(.gray)
Text(customer.customerName)
}
}
.onDelete(perform: deleteCustomer)
}
.padding()
Spacer()
}
.padding()
}
private func loadCustomerInfo() -> [E28CustomInfo] {
if let data = UserDefaults.standard.data(forKey: arrayKey),
let products = try? JSONDecoder().decode([E28CustomInfo].self, from: data) {
return products
}
return []
}
private func addCustomer() {
if let number = Int(customerNo) {
let newCustomer = E28CustomInfo(customerName: customerName, customerNumber: number)
savedCustomer.append(newCustomer)
saveCustomer()
customerName = ""
customerNo = ""
}
}
private func deleteCustomer(at offsets: IndexSet) {
savedCustomer.remove(atOffsets: offsets)
saveCustomer()
}
private func saveCustomer() {
if let encodedData = try? JSONEncoder().encode(savedCustomer) {
UserDefaults.standard.set(encodedData, forKey: arrayKey)
}
}
}
"""
//ポイント
let pointEssentials28 = """
プレビューでも永続的にデータが保存されてるか
を確認したいなら、
@AppStorageを使ってUserDafaultsかApp Groupsに格納した方がいい
ってのが分かるね。
オイラの個人的な経験と感想だけど、
安全に丁寧に仕事をする職人さんで、ビュー単体の確認もせずに、いきなりシミュレータで実行して初めてデータが格納されてるって人を見たことも聞いたこともないので、
少なくともAppStorageでやった方が安全
ってのは分かるよね🧐
@AppStorageおよび@SceneStorageに保存できるのは、特定の型の値のみ。具体的には、以下の6つ
◻︎Bool
◻︎Int
◻︎Double
◻︎String
◻︎URL
◻︎Data
以外を扱うときはカスタムデータ用の処理を追加する必要あり
"""
//URL
let urlEssentials28 = "https://note.com/m_kakudo/n/n1001be3acd76"
//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentialsCh28: Identifiable {
var id: Int
var title: String
var view: ViewEnumiOSApp17DevelopmentEssentialsCh28
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentialsCh28{
case Sec1
}
//各項目に表示するリスト項目
let dataiOSApp17DevelopmentEssentialsCh28: [ListiOSApp17DevelopmentEssentialsCh28] = [
ListiOSApp17DevelopmentEssentialsCh28(id: 1, title: essentialsChapter28SubTitle, view: .Sec1),
]
struct iOSApp17DevelopmentEssentialsCh28: View {
var body: some View {
VStack {
Divider()
List (dataiOSApp17DevelopmentEssentialsCh28) { data in
self.containedViewiOSApp17DevelopmentEssentialsCh28(dataiOSApp17DevelopmentEssentialsCh28: data)
}
.edgesIgnoringSafeArea([.bottom])
}
.navigationTitle(essentialsChapter28NavigationTitle)
.navigationBarTitleDisplayMode(.inline)
}
//タップ後に遷移先へ遷移させる関数
func containedViewiOSApp17DevelopmentEssentialsCh28(dataiOSApp17DevelopmentEssentialsCh28: ListiOSApp17DevelopmentEssentialsCh28) -> AnyView {
switch dataiOSApp17DevelopmentEssentialsCh28.view {
case .Sec1:
return AnyView(NavigationLink (destination: Essentials28()) {
Text(dataiOSApp17DevelopmentEssentialsCh28.title)
})
}
}
}
#Preview {
iOSApp17DevelopmentEssentialsCh28()
}
struct Essentials28: View {
var body: some View {
VStack{
TabView {
Essentials28ContensView()
.tabItem {
Image(systemName: contentsImageTab)
Text(contentsTextTab)
}
Essentials28Code()
.tabItem {
Image(systemName: codeImageTab)
Text(codeTextTab)
}
Essentials28Points()
.tabItem {
Image(systemName: pointImageTab)
Text(pointTextTab)
}
Essentials28WEB()
.tabItem {
Image(systemName: webImageTab)
Text(webTextTab)
}
}
}
}
}
#Preview {
Essentials28()
}
struct Essentials28Code: View {
var body: some View {
ScrollView{
Text(codeEssentials28)
}
}
}
#Preview {
Essentials28Code()
}
struct Essentials28Points: View {
var body: some View {
ScrollView{
Text(pointEssentials28)
}
}
}
#Preview {
Essentials28Points()
}
struct Essentials28WebView: UIViewRepresentable {
let searchURL: URL
func makeUIView(context: Context) -> WKWebView {
let view = WKWebView()
let request = URLRequest(url: searchURL)
view.load(request)
return view
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct Essentials28WEB: View {
private var url:URL = URL(string: urlEssentials28)!
var body: some View {Essentials28WebView(searchURL: url)
}
}
#Preview {
Essentials28WEB()
}
struct Essentials28ContensView: View {
var body: some View {
VStack{
TabView{
Essentials28StandardView()
.tabItem {
Image(systemName: "sun.max")
Text("基本")
}
E28SceneStorageView()
.tabItem {
Image(systemName: "cloud")
Text("SceneStorage")
}
E28AppStorageAppGroupsView()
.tabItem {
Image(systemName: "cloud.rain")
Text("AppGroups")
}
E28AppStorageUserDefaultsView()
.tabItem {
Image(systemName: "apple.logo")
Text("UserDefaults")
}
E28CustomeDataView()
.tabItem {
Image(systemName: "note")
Text("カスタムリスト")
}
}
}
}
}
struct Essentials28StandardView: View {
//@SceneStorage
@SceneStorage("menu") var menu:String = ""
//@AppStorage:App Groups
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) var fruits: String = ""
//@AppStorage:UserDafaults
@AppStorage("basket") var basket: String = ""
var body: some View {
ScrollView{
VStack{
TextField("項目を入力してください",text: $menu)
TextField("果物名を入力してください",text: $fruits)
TextField("かごを入力してください",text: $basket)
}
}
}
}
struct E28SceneStorageView: View {
@SceneStorage("menu") private var menu: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $menu)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageAppGroupsView: View {
@AppStorage("fruits",store: UserDefaults(suiteName: "group.iOS17.M_Kaku")) private var fruits: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $fruits)
.padding(35)
.font(.largeTitle)
}
}
}
struct E28AppStorageUserDefaultsView: View {
@AppStorage("basket") private var basket: String = ""
var body: some View {
VStack{
TextField("項目を入力",text: $basket)
.padding(35)
.font(.largeTitle)
}
}
}
#Preview {
Essentials28ContensView()
}
struct E28CustomInfo: Codable, Identifiable {
var id = UUID() // 一意の識別子を提供
let customerName: String //顧客名
let customerNumber: Int //顧客番号
}
struct E28CustomeDataView: View {
private let arrayKey = "顧客情報保存"
@State private var savedCustomer: [E28CustomInfo] = []
@State private var customerName: String = ""
@State private var customerNo: String = ""
init() {
_savedCustomer = State(initialValue: loadCustomerInfo())
}
var body: some View {
VStack {
HStack {
TextField("ID", text: $customerNo)
.padding()
.keyboardType(.numberPad)
TextField("なまえ", text: $customerName)
.padding()
}
Button("顧客情報追加") {
addCustomer()
}
.padding()
List {
ForEach(savedCustomer) { customer in
HStack{
Text("No.\(customer.customerNumber):")
.foregroundColor(.gray)
Text(customer.customerName)
}
}
.onDelete(perform: deleteCustomer)
}
.padding()
Spacer()
}
.padding()
}
private func loadCustomerInfo() -> [E28CustomInfo] {
if let data = UserDefaults.standard.data(forKey: arrayKey),
let products = try? JSONDecoder().decode([E28CustomInfo].self, from: data) {
return products
}
return []
}
private func addCustomer() {
if let number = Int(customerNo) {
let newCustomer = E28CustomInfo(customerName: customerName, customerNumber: number)
savedCustomer.append(newCustomer)
saveCustomer()
customerName = ""
customerNo = ""
}
}
private func deleteCustomer(at offsets: IndexSet) {
savedCustomer.remove(atOffsets: offsets)
saveCustomer()
}
private func saveCustomer() {
if let encodedData = try? JSONEncoder().encode(savedCustomer) {
UserDefaults.standard.set(encodedData, forKey: arrayKey)
}
}
}
◾️EssentialsMenu.swift
//フレームワーク
import SwiftUI
import WebKit
//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentials: Identifiable {
var id: Int
var title: String
var view: ViewEnumiOSApp17DevelopmentEssentials
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentials {
case Ch1
//じっくり13で追加
case Ch2
//じっくり14で追加
case Ch3
//じっくり15で追加
case Ch4
//じっくり16で追加
case Ch5
//じっくり17で追加
case Ch6
//じっくり18で追加
case Ch7
//じっくり19で追加
case Ch8
//じっくり20、21で追加
case Ch9
//じっくり22、23で追加
case Ch10
//じっくり24で追加
case Ch11
//じっくり25で追加
case Ch12
//じっくり26で追加
case Ch13
//じっくり27,28で追加
case Ch14
//じっくり29で追加
case Ch15
//じっくり31で追加
case Ch16
//じっくり32で追加
case Ch17
//じっくり33で追加
case Ch18
//じっくり34で追加
case Ch19
//じっくり35で追加
case Ch20
//じっくり36で追加
case Ch21
//じっくり37で追加
case Ch22
//じっくり40で追加
case Ch23
//じっくり41で追加
case Ch24
//じっくり43で追加
case Ch25
//じっくり44で追加
case Ch26
//じっくり45で追加
case Ch27
//じっくり46で追加
case Ch28
}
//各項目に表示する文字列
let dataiOSApp17DevelopmentEssentials: [ListiOSApp17DevelopmentEssentials] = [
ListiOSApp17DevelopmentEssentials(id: 1, title: essentialsChapter1Title, view: .Ch1),
//じっくり13で追加
ListiOSApp17DevelopmentEssentials(id: 2, title: essentialsChapter2Title, view: .Ch2),
//じっくり13で追加
ListiOSApp17DevelopmentEssentials(id: 3, title: essentialsChapter3Title, view: .Ch3),
//じっくり15で追加
ListiOSApp17DevelopmentEssentials(id: 4, title: essentialsChapter4Title, view: .Ch4),
//じっくり16で追加
ListiOSApp17DevelopmentEssentials(id: 5, title: essentialsChapter5Title, view: .Ch5),
//じっくり17で追加
ListiOSApp17DevelopmentEssentials(id: 6, title: essentialsChapter6Title, view: .Ch6),
//じっくり18で追加
ListiOSApp17DevelopmentEssentials(id: 7, title: essentialsChapter7Title, view: .Ch7),
//じっくり19で追加
ListiOSApp17DevelopmentEssentials(id: 8, title: essentialsChapter8Title, view: .Ch8),
//じっくり20、21で追加
ListiOSApp17DevelopmentEssentials(id: 9, title: essentialsChapter9Title, view: .Ch9),
//じっくり22、23で追加
ListiOSApp17DevelopmentEssentials(id: 10, title: essentialsChapter10Title, view: .Ch10),
//じっくり24で追加
ListiOSApp17DevelopmentEssentials(id: 11, title: essentialsChapter11Title, view: .Ch11),
//じっくり25で追加
ListiOSApp17DevelopmentEssentials(id: 12, title: essentialsChapter12Title, view: .Ch12),
//じっくり26で追加
ListiOSApp17DevelopmentEssentials(id: 13, title: essentialsChapter13Title, view: .Ch13),
//じっくり27,28で追加
ListiOSApp17DevelopmentEssentials(id: 14, title: essentialsChapter14Title, view: .Ch14),
//じっくり29で追加
ListiOSApp17DevelopmentEssentials(id: 15, title: essentialsChapter15Title, view: .Ch15),
//じっくり31で追加
ListiOSApp17DevelopmentEssentials(id: 16, title: essentialsChapter16Title, view: .Ch16),
//じっくり32で追加
ListiOSApp17DevelopmentEssentials(id: 17, title: essentialsChapter17Title, view: .Ch17),
//じっくり33で追加
ListiOSApp17DevelopmentEssentials(id: 18, title: essentialsChapter18Title, view: .Ch18),
//じっくり34で追加
ListiOSApp17DevelopmentEssentials(id: 19, title: essentialsChapter19Title, view: .Ch19),
//じっくり35で追加
ListiOSApp17DevelopmentEssentials(id: 20, title: essentialsChapter20Title, view: .Ch20),
//じっくり36で追加
ListiOSApp17DevelopmentEssentials(id: 21, title: essentialsChapter21Title, view: .Ch21),
//じっくり37で追加
ListiOSApp17DevelopmentEssentials(id: 22, title: essentialsChapter22Title, view: .Ch22),
//じっくり40で追加
ListiOSApp17DevelopmentEssentials(id: 23, title: essentialsChapter23Title, view: .Ch23),
//じっくり41で追加
ListiOSApp17DevelopmentEssentials(id: 24, title: essentialsChapter24Title, view: .Ch24),
//じっくり43で追加
ListiOSApp17DevelopmentEssentials(id: 25, title: essentialsChapter25Title, view: .Ch25),
//じっくり44で追加
ListiOSApp17DevelopmentEssentials(id: 26, title: essentialsChapter26Title, view: .Ch26),
//じっくり47で追加
ListiOSApp17DevelopmentEssentials(id: 27, title: essentialsChapter27Title, view: .Ch27),
//じっくり48で追加
ListiOSApp17DevelopmentEssentials(id: 28, title: essentialsChapter28Title, view: .Ch28),
]
struct iOSApp17DevelopmentEssentials: View {
var body: some View {
VStack {
Divider()
List (dataiOSApp17DevelopmentEssentials) { data in
self.containedViewiOSApp17DevelopmentEssentials(dataiOSApp17DevelopmentEssentials: data)
}
.edgesIgnoringSafeArea([.bottom])
}
.navigationTitle("iOS開発の章目次")
.navigationBarTitleDisplayMode(.inline)
}
//タップ後に遷移先へ遷移させる関数
func containedViewiOSApp17DevelopmentEssentials(dataiOSApp17DevelopmentEssentials: ListiOSApp17DevelopmentEssentials) -> AnyView {
switch dataiOSApp17DevelopmentEssentials.view {
case .Ch1:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh1()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり13で追加
case .Ch2:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh2()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり13で追加
case .Ch3:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh3()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり15で追加
case .Ch4:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh4()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり16で追加
case .Ch5:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh5()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり17で追加
case .Ch6:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh6()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり18で追加
case .Ch7:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh7()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり19で追加
case .Ch8:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh8()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり20、21で追加
case .Ch9:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh9()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり22、23で追加
case .Ch10:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh10()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり24で追加
case .Ch11:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh11()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり25で追加
case .Ch12:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh12()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり26で追加
case .Ch13:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh13()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり27,28で追加
case .Ch14:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh14()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり29で追加
case .Ch15:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh15()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり31で追加
case .Ch16:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh16()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり32で追加
case .Ch17:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh17()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり33で追加
case .Ch18:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh18()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり34で追加
case .Ch19:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh19()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり35で追加
case .Ch20:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh20()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり36で追加
case .Ch21:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh21()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり37で追加
case .Ch22:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh22()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり40で追加
case .Ch23:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh23()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり41で追加
case .Ch24:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh24()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり43で追加
case .Ch25:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh25()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり44で追加
case .Ch26:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh26()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり45で追加
case .Ch27:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh27()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
//じっくり46で追加
case .Ch28:
return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh28()) {
Text(dataiOSApp17DevelopmentEssentials.title)
})
}
}
}
#Preview {
iOSApp17DevelopmentEssentials()
}
以上。
ま、これを使うと応用で
アプリの顧客の購入リストなんかもこういう機能を使って作ってる
ってのが分かるでしょ🧐
さてと、温泉に〜〜〜😛
じゃまたね〜〜〜