//
import SwiftUI
import RealmSwift
struct HomeView: View {
@ObservedResults(MemoDataModel.self) var memoDataList
@State var isShowColorSetting = false
@EnvironmentObject var myColor: MyColor
var body: some View {
NavigationStack {
VStack {
memoList
// Spacerを下に入れることで、メモ一覧を上に詰めるように表示させる
Spacer()
}
.toolbarBackground(myColor.color, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
// NavigationBarに設置するボタンなどを定義
.toolbar {
// 左上:テーマカラー設定
ToolbarItem(placement: .navigationBarLeading) {
colorSettingButton
}
// 右上:新規メモ作成
ToolbarItem(placement: .navigationBarTrailing) {
memoCreateLinkButton
}
}
}
}
/// メモ一覧.
var memoList: some View {
List {
ForEach(memoDataList, id: \.self) { memo in
// NavigationLinkは画面遷移を行うViewです
// タップでdestinationに記載したViewへNavigation遷移
NavigationLink(
// 画面遷移する際にタップしたセルのMemoDataを渡す
destination:
MemoDetailView(memoData: memo)
) {
memoCell(memo)
}
}
.onDelete(perform: delete)
}
.listStyle(.plain)
}
/// カラー設定ボタン.
var colorSettingButton: some View {
Button(
action: {
isShowColorSetting = true
}, label: {
Image(.colorSettingIcon)
}
)
.confirmationDialog("テーマカラーを選択してください", isPresented: $isShowColorSetting) {
ForEach(MyColorType.allCases, id: \.self) { type in
Button(
action: {
myColor.changeThemeColor(type: type)
}, label: {
Text(type.title)
}
)
}
Button("キャンセル", role: .cancel) {}
}
}
/// メモ作成ボタン.
var memoCreateLinkButton: some View {
// NavigationLinkはTextだけでなく画像やViewでも設定できる
NavigationLink(
// onAppearで生成した新しいMemoDataを渡す
destination:
MemoDetailView(memoData: MemoDataModel())
) {
Image(systemName: "plus")
}
}
}
// MARK: View Components
extension HomeView {
/// メモ一覧に表示するセル.
func memoCell(_ memo: MemoDataModel) -> some View {
VStack(alignment: .leading) {
// メモのテキスト
Text(memo.text)
.font(.body)
// メモの作成・編集日時
Text(formatDate.string(from: memo.recordDate))
.font(.footnote)
}
}
}
extension HomeView {
// 表示形式を変更した日付
var formatDate: DateFormatter {
let formatter = DateFormatter()
// Apple が推奨する固定フォーマット用ロケール
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "UTC")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
return formatter
}
}
// MARK: Methods
extension HomeView {
/// リストからメモを削除する処理(Realm オブジェクトを削除).
func delete(at offsets: IndexSet) {
// map + firstで削除対象のオブジェクトを取得
if let objectToDelete = offsets
// インデックスを使って、対応するメモデータを取り出す
.map({ memoDataList[$0] })
// 今回は最初の1件だけを削除対象にする
.first {
// removeでリストからメモデータを削除
self.$memoDataList.remove(objectToDelete)
}
}
}
#Preview {
@Previewable @StateObject var myColor = MyColor()
HomeView()
.environmentObject(myColor)
}
#Preview {
@Previewable @StateObject var myColor = MyColor()
HomeView()
.environmentObject(myColor)
}
//
import SwiftUI
import RealmSwift
struct MemoDetailView: View {
@EnvironmentObject var myColor: MyColor
/// 新規作成または編集するメモの情報.
/// Binding($○○)で値を変更すると、自動的にRealmにも反映される
@ObservedRealmObject var memoData: MemoDataModel
/// TextEditorのフォーカス.
@FocusState var isFocused: Bool
var body: some View {
TextEditor(text: $memoData.text)
.focused($isFocused)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
HStack {
Spacer()
keyboardCloseButton
}
}
}
.navigationTitle(dateFormatter.string(from: memoData.recordDate))
.toolbarBackground(myColor.color, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.onDisappear {
// 新規作成されたメモかどうかを判定
if memoData.realm == nil {
// テキストが空でなければ保存(空のままなら保存しない)
if !memoData.text.isEmpty {
saveData()
}
} else {
// 既存メモの場合は、最終編集日時だけ更新
updateDate()
}
}
}
/// キーボードを閉じるボタン.
var keyboardCloseButton: some View {
Button(
action: {
isFocused = false
}, label: {
Text("done")
}
)
}
}
extension MemoDetailView {
/// Date型の値を年月日に表示を変換する.
var dateFormatter: DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy年MM月dd日"
return dateFormatter
}
/// Realmにメモデータを保存する.
func saveData() {
let realm = try! Realm()
try! realm.write {
memoData.recordDate = Date()
realm.add(memoData)
}
print(memoData)
}
/// メモデータの日付を更新する.
func updateDate() {
// Bindingでない場合のRealmオブジェクトの更新
// 本来Realmオブジェクトは安全性のため編集不可なので、
// thawで解凍し、解凍したオブジェクトのwrite内で更新する
let data = memoData.thaw()
try! data?.realm?.write {
// 解凍後のデータを使って、recordDateを更新
data?.recordDate = Date()
}
}
}
#Preview {
@Previewable @StateObject var myColor = MyColor()
@Previewable let memo = MemoDataModel()
MemoDetailView(memoData: MemoDataModel())
.environmentObject(myColor)
}
//
import SwiftUI
enum MyColorType: CaseIterable {
case base // #FFFFFF
case orange // #F8C165
case red // #D24141
case blue // #4187FA
case pink // #F064B9
case green // #50AA41
case purple // #965AD2
var color: Color {
switch self {
case .base: return .white
case .orange: return Color.rgbo(red: 248, green: 193, blue: 101, opacity: 1)
case .red: return Color.rgbo(red: 210, green: 65, blue: 65, opacity: 1)
case .blue: return Color.rgbo(red: 65, green: 135, blue: 250, opacity: 1)
case .pink: return Color.rgbo(red: 240, green: 100, blue: 185, opacity: 1)
case .green: return Color.rgbo(red: 80, green: 170, blue: 65, opacity: 1)
case .purple: return Color.rgbo(red: 150, green: 90, blue: 210, opacity: 1)
}
}
var title: String {
switch self {
case .base: return "デフォルト"
case .orange: return "オレンジ"
case .red: return "レッド"
case .blue: return "ブルー"
case .pink: return "ピンク"
case .green: return "グリーン"
case .purple: return "パープル"
}
}
}
extension Color {
static func rgbo(red: Int, green: Int, blue: Int, opacity: CGFloat = 1) -> Color {
return Color(red: CGFloat(red) / 255, green: CGFloat(green) / 255, blue: CGFloat(blue) / 255, opacity: opacity)
}
}