I tried to save the image in my memo with as UIImage as Codable protocol.
My code can be a little bit longer, but please refer to it.
1. first one is the NewMemoView that I can make my memo with ImagePicker.
import SwiftUI
struct NewMemoView: View {
let folder : FolderModel
let memo : MemoModel?
#State private var items : [Any] = []
#EnvironmentObject var vm : MemoViewModel
#Environment(\.presentationMode) var presentationMode
#State private var title : String = ""
#State private var content : String = ""
#State private var color : Color = .mint
#State private var showAlert : Bool = false
#State private var pickedImportance : String = "Anytime 🐳"
#State private var isEditMode : Bool = false
#State private var isSecret : Bool = false
#State private var password : String = ""
#State private var showShareSheet : Bool = false
#State private var showImagePicker : Bool = false
#State private var selectedImage : UIImage?
#State private var tappedImage : Bool = false
#State private var showDeleteImageOption : Bool = false
let importances = ["Anytime 🐳", "Normal ☀️", "Important 🔥"]
var someImage : SomeImage? {
guard let image = selectedImage else {return nil}
return SomeImage(image: image)
}
init(memo : MemoModel?, folder : FolderModel) {
self.folder = folder
self.memo = memo
if let memo = memo {
_title = State(initialValue: memo.title)
_content = State(initialValue: memo.content)
_color = State(initialValue: memo.color)
_pickedImportance = State(initialValue: memo.isImportant)
_isEditMode = State(initialValue: true)
_isSecret = State(initialValue: memo.isSecret)
_password = State(initialValue: memo.password ?? "")
_selectedImage = State(initialValue: memo.image?.image)
}
}
var body: some View {
Form {
Section(header : Text("Title 🔖")) {
TextField("Input the title", text: $title)
.autocapitalization(.none)
.disableAutocorrection(true)
}
Section(header : Text("Importance ✅")) {
Picker("", selection: $pickedImportance) {
ForEach(importances, id: \.self) {
Text($0)
}
}
.pickerStyle(.segmented)
}
Section(header : Text("Content ✏️(총 \(content.count) 자)")) {
TextEditor(text: $content)
.autocapitalization(.none)
.disableAutocorrection(true)
.frame(maxWidth : .infinity)
.frame(height : UIScreen.main.bounds.height * 0.25)
}
.onTapGesture {
UIApplication.shared.closeKeyboard()
}
Section(header : Text("Image List 🌅")) {
HStack {
if let selectedImage = selectedImage {
Image(uiImage: selectedImage)
.resizable()
.scaledToFill()
.frame(width : 50, height : 50)
.clipShape(RoundedRectangle(cornerRadius: 12))
.onTapGesture {
tappedImage.toggle()
}
.onLongPressGesture(perform: {
showDeleteImageOption.toggle()
})
.confirmationDialog(Text("Delete Image"), isPresented: $showDeleteImageOption, actions: {
Button(role : .destructive, action: {
vm.deleteImage(folder: folder, memo: memo!, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password)
}, label: {
Text("Delete Image")
})
})
.sheet(isPresented: $tappedImage) {
ImageView(image: self.selectedImage!)
}
} else {
Text("You can upload only 'one' Image")
.foregroundColor(.gray)
}
}
.padding(8)
}
Section(header : Text("Memo Settings ⚙️")) {
ColorPicker("Memo Color", selection: $color, supportsOpacity: false)
Toggle(isOn: $isSecret, label: {
Text("Enable Secret Mode")
})
if isSecret {
HStack {
Text("Password : ")
Spacer()
TextField("Input", text: $password)
}
}
}
Button(action: {
if isEditMode {
if let someImage = someImage {
if vm.checkMemoStatus(title: title, content: content) {
vm.editMemo(folder: folder, memo: memo!, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password, image : someImage)
presentationMode.wrappedValue.dismiss()
} else {
vm.editMemo(folder: folder, memo: memo!, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password, image : nil)
presentationMode.wrappedValue.dismiss()
}
} else {
self.showAlert.toggle()
}
} else {
if vm.checkMemoStatus(title: title, content: content) {
if let someImage = someImage {
vm.addMemo(folder: folder, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password, image: someImage)
presentationMode.wrappedValue.dismiss()
} else {
vm.addMemo(folder: folder, title: title, content: content, color: color, isImportant: pickedImportance, isSecret: isSecret, password: password, image: nil)
presentationMode.wrappedValue.dismiss()
}
} else {
self.showAlert.toggle()
}
}
}, label: {
Text(isEditMode ? "Edit".uppercased() : "save".uppercased())
.fontWeight(.bold)
})
.alert(isPresented : $showAlert) {
Alert(title: Text("Warning 🚫"), message: Text("Check your title and content, please."), dismissButton: .cancel())
}
}//form
.navigationTitle("Add Memo 📒")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
share()
}, label: {
Image(systemName: "square.and.arrow.up")
})
.disabled(!isEditMode)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showImagePicker.toggle()
}, label: {
Image(systemName: "photo")
})
.sheet(isPresented: $showImagePicker) {
ImagePicker(image: $selectedImage)
}
}
}
}
}
extension NewMemoView {
func share() {
let content = memo?.content
let image = memo?.image?.image
let vc = UIActivityViewController(activityItems: [image, content], applicationActivities: nil)
UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: true)
}
}
2. Second one is the viewModel for memos.
import SwiftUI
import Foundation
class MemoViewModel : ObservableObject {
#Published var sortedMode : SortedMode?
#Published var folders : [FolderModel] = [] {
didSet {
saveData()
}
}
init() {
getData()
}
let foldersKey = "Folders_key"
func saveData() {
if let encodedData = try? JSONEncoder().encode(folders) {
UserDefaults.standard.set(encodedData, forKey: foldersKey)
}
}
func getData() {
guard let data = UserDefaults.standard.data(forKey: foldersKey),
let savedData = try? JSONDecoder().decode([FolderModel].self, from: data)
else {return}
self.folders = savedData
}
func addNewFolder(folderName : String) {
let newFolder = FolderModel(folderName: folderName)
folders.append(newFolder)
}
func deleteFolder(folder : FolderModel) {
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
folders.remove(at: index)
}
}
func deleteFolder(indexSet : IndexSet) {
folders.remove(atOffsets: indexSet)
}
func checkFolderNameCount(folderName : String) -> Bool {
if folderName.count < 3 {
return false
} else {
return true
}
}
func checkMemoStatus(title : String, content : String) -> Bool {
if title.count == 0 || content.count == 0 {
return false
} else {
return true
}
}
func addMemo(folder : FolderModel, title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String, image : SomeImage?) {
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
let newMemo = MemoModel(title : title, content : content, color : color, isImportant: isImportant, isSecret: isSecret, password: password, image: image)
folders[index].memo.append(newMemo)
}
}
func editMemo(folder : FolderModel, memo : MemoModel, title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String, image : SomeImage?) {
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
if let index1 = folders[index].memo.firstIndex(where: {$0.id == memo.id}) {
folders[index].memo[index1] = memo.editMemo(title: title, content: content, color: color, isImportant: isImportant, isSecret: isSecret, password: password, image : image)
}
}
}
func deleteMemo(folder : FolderModel, memo : MemoModel) {
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
if let index1 = folders[index].memo.firstIndex(where: {$0.id == memo.id}) {
folders[index].memo.remove(at: index1)
}
}
}
func deleteImage(folder : FolderModel, memo : MemoModel, title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String) {
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
if let index1 = folders[index].memo.firstIndex(where: {$0.id == memo.id}) {
folders[index].memo[index1] = memo.deleteImage(title: title, content: content, color: color, isImportant: isImportant, isSecret: isSecret, password: password)
}
}
}
func checkPassword(memo : MemoModel, password : String) -> Bool {
if memo.password == password {
return true
} else {
return false
}
}
func sortedMemos(folder : FolderModel) {
if let sortedMode = sortedMode {
switch sortedMode {
case .moreImportant:
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
folders[index].memo.sort(by: {$0.isImportant > $1.isImportant})
}
case .faster:
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
folders[index].memo.sort(by: {$0.timeStamp < $1.timeStamp})
}
case .later:
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
folders[index].memo.sort(by: {$0.timeStamp > $1.timeStamp})
}
case .lessImportant:
if let index = folders.firstIndex(where: {$0.id == folder.id}) {
folders[index].memo.sort(by: {$0.isImportant > $1.isImportant})
}
}
}
}
}
enum SortedMode {
case moreImportant, faster, later, lessImportant
}
3. Last one is the codable model for this memo App
import SwiftUI
import Foundation
struct FolderModel : Identifiable, Codable {
var id = UUID()
var folderName : String
var memo : [MemoModel] = []
}
struct MemoModel : Identifiable, Codable {
var id = UUID()
var title : String = "제목 없음"
var content : String
var color : Color = .gray
var timeStamp : String = Date().formatted(date: .numeric, time: .omitted)
var isImportant : String
var isSecret : Bool = false
var password : String?
var image : SomeImage?
func editMemo(title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String, image : SomeImage?) -> MemoModel {
return MemoModel(id: id, title: title, content: content, color: color, isImportant: isImportant, isSecret: isSecret, password: password, image: image)
}
func deleteImage(title : String, content : String, color : Color, isImportant : String, isSecret : Bool, password : String) -> MemoModel {
return MemoModel(id: id, title: title, content: content, color: color, isImportant: isImportant, isSecret: isSecret, password: password, image: nil)
}
}
struct SomeImage : Identifiable, Codable {
var id = UUID()
var image: UIImage?
init(image: UIImage) {
self.image = image
}
enum CodingKeys: CodingKey {
case data
case scale
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let scale = try container.decode(CGFloat.self, forKey: .scale)
let data = try container.decode(Data.self, forKey: .data)
self.image = UIImage(data: data, scale: scale)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let image = self.image {
try container.encode(image.pngData(), forKey: .data)
try container.encode(image.scale, forKey: .scale)
}
}
}
I will summarize my point!
When I try to delete the image by deleteMemo, giving nil to image, the NewMemoView is shut down. I don't know why, but it it doing.
When I tap the save button with image, there is some lagging, how I can handle this situation? I think maybe that most of image has bigger capacity than text.
Thanks!
import SwiftUI
struct AddChildrenView: View {
#State var word : String = ""
#State var meaning : String = ""
#State var isShowAlert : Bool = false
#State var isClicked : Bool = false
#State var searchText : String = ""
#EnvironmentObject var itemModel : ItemModel
var item : Item
var searchItem : [Children] {
if searchText.isEmpty {
return item.children
} else {
return item.children.filter({$0.word!.contains(searchText)})
}
}
init() {
UINavigationBar.appearance().titleTextAttributes = [.font : UIFont(name: "NotoSans-Bold", size: 20)!]
}
var body: some View {
NavigationView {
ZStack {
if item.children.count == 0 {
NochildrenView()
} else {
List {
ForEach(searchItem) {child in
WordListRowView(children: child)
.swipeActions(edge: .trailing, allowsFullSwipe: true, content: {
Button(role: .destructive, action: {
itemModel.deleteChildren(children: child, item: item)
}, label: {
Image(systemName: "trash.fill")
})
Button(action: {
itemModel.favoriteChildren(item: item, children: child)
}, label: {
Image(systemName: "star.circle.fill")
.font(.title3)
})
.tint(.green)
})
}
}
.listStyle(.insetGrouped)
.padding(.top, -10)
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
.autocapitalization(.none)
}
}
.navigationBarTitle("\(item.group!)'s Words")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
HStack {
Button(action: {
addChildren()
}, label: {
Image(systemName: "plus")
})
Button(action: {
itemModel.shuffleChildren(item: item)
}, label: {
Image(systemName: "shuffle")
})
NavigationLink(destination: FlashCardView(item: item)) {
Image(systemName: "play.rectangle")
}
.disabled(item.children.isEmpty)
})
}
}
}
extension AddChildrenView {
func addChildren() {
let alert = UIAlertController(title: "Saving word", message: "Type word and meaning 😀", preferredStyle: .alert)
alert.addTextField { word in
word.placeholder = "Type a word"
word.keyboardType = .alphabet
}
alert.addTextField { meaning in
meaning.placeholder = "a meaning of word"
}
let addfolderAction = UIAlertAction(title: "Add", style: .default, handler: {
(_) in
self.word = alert.textFields![0].text!
self.meaning = alert.textFields![1].text!
itemModel.addNewChildren(item: item, word: word, meaning: meaning)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
(_) in
})
alert.addAction(cancelAction)
alert.addAction(addfolderAction)
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
Hello, first, this view is the second view after click the navigationLink in firstView.
init() {
UINavigationBar.appearance().titleTextAttributes = [.font : UIFont(name: "NotoSans-Bold", size: 20)!]
}
I tried this code to use custom font on my navigationBartitle, but there is error like this.
"Return from initializer without initializing all stored properties"
Could you let me know the way I can solve this problem?
Thank you!
This is my NavigationLink DetailView,
import SwiftUI
struct AddChildrenView: View {
#State var word : String = ""
#State var meaning : String = ""
#State var isShowAlert : Bool = false
#State var isClicked : Bool = false
#EnvironmentObject var itemModel : ItemModel
var item : Item
var body: some View {
ZStack {
if item.children.count == 0 {
NochildrenView()
}
List {
ForEach(item.children) { child in
WordListRowView(children: child)
.onTapGesture {
withAnimation(.linear(duration: 0.3)) {
itemModel.favoriteChildren(item: item, children: child)
}
}
}
.onDelete { indexSet in
itemModel.deleteChildren1(item: item, indexSet: indexSet)
}
}
.id(UUID())
}
.navigationBarTitle("\(item.group!)'s Words")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
HStack {
Button(action: {
addChildren()
}, label: {
Image(systemName: "plus")
})
Button(action: {
itemModel.shuffleChildren(item: item)
}, label: {
Image(systemName: "shuffle")
})
NavigationLink(destination: FlashCardView(item: item)) {
Image(systemName: "play.rectangle")
}
.disabled(item.children.isEmpty)
})
}
}
extension AddChildrenView {
func addChildren() {
let alert = UIAlertController(title: "Saving word", message: "Type word and meaning 😀", preferredStyle: .alert)
alert.addTextField { word in
word.placeholder = "Type a word"
}
alert.addTextField { meaning in
meaning.placeholder = "a meaning of word"
}
let addfolderAction = UIAlertAction(title: "Add", style: .default, handler: {
(_) in
self.word = alert.textFields![0].text!
self.meaning = alert.textFields![1].text!
itemModel.addNewChildren(item: item, word: word, meaning: meaning)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
(_) in
})
alert.addAction(addfolderAction)
alert.addAction(cancelAction)
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
This is MotherView with NavigationLink,
import SwiftUI
struct HomeView: View {
#State var folderName : String = ""
#EnvironmentObject var itemModel : ItemModel
var body: some View {
NavigationView{
ZStack {
if itemModel.items.count == 0 {
NoItemView()
}
List{
ForEach(itemModel.items) {item in
if let group = item.group {
NavigationLink(destination: {
AddChildrenView(item : item)
}, label: {
HStack{
Image(systemName: "folder")
.foregroundColor(.yellow)
Text(group)
.font(.headline)
.lineLimit(1)
}
})
}
}
.onMove(perform: itemModel.moveItem)
.onDelete(perform: itemModel.deleteItem)
}
.listStyle(.plain)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle("SelfDic 📒")
.navigationBarItems(trailing:
Button(action: {
addFolderView()
}, label: {
Image(systemName: "folder.badge.plus")
}))
.navigationBarItems(leading: EditButton())
}
}
}
extension HomeView {
func addFolderView() {
let alert = UIAlertController(title: "Saving folder", message: "Type a name of folder 🙂", preferredStyle: .alert)
alert.addTextField { name in
name.placeholder = "folder's name"
}
let addfolderAction = UIAlertAction(title: "Add", style: .default, handler: {
(_) in
self.folderName = alert.textFields![0].text!
itemModel.addNewFolder(text: folderName)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
(_) in
})
alert.addAction(addfolderAction)
alert.addAction(cancelAction)
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
**I wanted to use Zstack in my linked View
The logic is that, If there is no item.children, I will show NoChildrenView().
But, It doesn't work.
How can I treat this problem?**
I hope I can solve this!
Thank you all senior developers!!
I didn't fully check all code, but in AddChildrenView there might be missing an else. Like you have it now the empty list will be always shown above your NochildrenView()
if item.children.count == 0 {
NochildrenView()
} else { // here
...
How can I optimize the list filtering? There are 2000 records in the list and when entering text into the application's search engine it clips a bit. Do you have any suggestions that could optimize the search?
Code
import CoreData
import SwiftUI
struct SongbookView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(
entity: Song.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Song.number, ascending: true)]
) var songs: FetchedResults<Song>
#State private var searchText = ""
var body: some View {
NavigationView{
VStack{
SearchBar(text: $searchText)
Spacer()
List(songs.filter({searchText.isEmpty ? true : removeNumber(str: $0.content!.lowercased()).contains(searchText.lowercased()) || String($0.number).contains(searchText)}), id:\.objectID) { song in
NavigationLink(destination: DetailView(song: song, isSelected: song.favorite)) {
HStack{
Text("\(String(song.number)). ") .font(.headline) + Text(song.title ?? "Brak tytułu")
if song.favorite {
Spacer()
Image(systemName: "heart.fill")
.accessibility(label: Text("To jest ulubiona pieśń"))
.foregroundColor(.red)
}
}.lineLimit(1)
}
}.id(UUID())
}
.listStyle(InsetListStyle())
.navigationTitle("Śpiewnik")
}
}
func removeNumber(str: String) -> String {
var result = str
let vowels: Set<Character> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
result.removeAll(where: { vowels.contains($0) })
return result
}
}
extension UINavigationController {
// Remove back button text
open override func viewWillLayoutSubviews() {
navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
}
I would like text from the dictinary to appear when the button is pressed.
"Text1" is there at the beginning. If the button is pressed, a label with Text2 should appear under the start label. Then automatically a label with Text3 after 3 seconds. Originally I tried a list, but unfortunately I didn't get it.
It would be great if someone has an idea.
struct ContentView: View {
var chats = [
"Text1" : "Hello?",
"Text2" : "Here!",
"Text3" : "And here!",
]
var body: some View {
NavigationView {
VStack {
Button(action: {}) {
Text("Button")
}
HStack {
Image (systemName: "faceid")
Text("\(chats["Text1"] ?? "Failure")")
}
}
}
}
}
Here is your solution. Please try this.
struct ContentView: View {
var chats: [String: String] = [
"Text1" : "Hello?",
"Text2" : "Here!",
"Text3" : "And here!",
]
#State var currentIndex: Int = 1
var body: some View {
NavigationView {
VStack {
Button(action: {
self.currentIndex = self.currentIndex + 1
if self.currentIndex == 2 {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.currentIndex = self.currentIndex + 1
}
}
print("Hello, World! Tapped")
}) {
Text("Hello, World!")
}
HStack {
Image (systemName: "faceid")
Text("\(chats["Text\(currentIndex)"] ?? "Failure")")
}
}
}
}
}