iOS_记事本开发

iOS_记事本开发

十二月 27, 2020

关于启动页和APP图标的设置

之前在更换启动页内容的时候发现更新启动页图片之后iOS虚拟设备未能成功设置为新的启动页。
这里可以参考这个文章末尾的方法:https://www.jianshu.com/p/d34c36cd15fc

关于页面跳转的一些问题

页面跳转的三种方式:https://blog.csdn.net/zcc9618/article/details/82968734

页面跳转利用prepare方法以及segue传输数据时identifier值为空的解决办法:https://www.hangge.com/blog/cache/detail_720.html

Swift使用CoreData储存数据

这里可以直接作为swift的一种数据库来理解。就是swift有一个叫coredata的数据库,创建项目的时候有个默认未勾选的选项use coredata就是是否为项目创建一个这个数据库文件,没有勾选的话可以在New File中手动选择添加,都一样。

详细操作过程可以参考这篇文章:https://www.sohu.com/a/223213477_663371
这里简单举例:

xcdatamodeld即为coredata的数据库文件。在name.xcdatamodeld文件中设置数据表。建表的话鼠标点击Add Entity即可。然后添加相关Attributes

20201228125341
20201228123752

一些小问题

代码中有些针对控件的操作需要用到控件的identifier来定位被操作的控件。控件的identifier默认一般为空,所以需要对其进行设置,设置的值和代码中定位控件用的值一样即可。这里用文本记事的例子举例:
20201228140046
20201228135652

目录结构

这里用的是分组!!!而非是文件夹!!!
20201228142526
20201228142356
20201228142425

文本记事

数据库操作

Note.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import Foundation
import SQLite3

// 文本记事的数据结构
struct Note {
let id: Int
var contents: String
}

// 文本记事数据库操作
class NoteManager {
var database: OpaquePointer!

static let main = NoteManager()

private init() {
}

func connect() {
if database != nil {
return
}
do {
let databaseURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("notes.sqlite3")
if sqlite3_open(databaseURL.path, &database) == SQLITE_OK {
if sqlite3_exec(database, "CREATE TABLE IF NOT EXISTS notes (contents TEXT)", nil, nil, nil) == SQLITE_OK {

} else {
print("Could not create table")
}
} else {
print("Could not connect")
}
}
catch let error {
print("Could not create database \(error)")
}
}

func create() -> Int {
connect()

var statement: OpaquePointer!
if sqlite3_prepare_v2(database, "INSERT INTO notes (contents) VALUES ('New note')", -1, &statement, nil) != SQLITE_OK {
print("Could not create query")
return -1
}

if sqlite3_step(statement) != SQLITE_DONE {
print("Could not insert note")
return -1
}

sqlite3_finalize(statement)
return Int(sqlite3_last_insert_rowid(database))
}

func getAllNotes() -> [Note] {
connect()
var result: [Note] = []

var statement: OpaquePointer!
if sqlite3_prepare_v2(database, "SELECT rowid, contents FROM notes", -1, &statement, nil) != SQLITE_OK {
print("Eroor creating select")
return []
}

while sqlite3_step(statement) == SQLITE_ROW {
result.append(Note(id: Int(sqlite3_column_int(statement, 0)), contents: String(cString: sqlite3_column_text(statement, 1))))
}

sqlite3_finalize(statement)
return result
}

func save(note: Note) {
connect()

var statement: OpaquePointer!
if sqlite3_prepare_v2(database, "UPDATE notes SET contents = ? WHERE rowid = ?", -1, &statement, nil) != SQLITE_OK {
print("Could not create update statement")
}

sqlite3_bind_text(statement, 1, NSString(string: note.contents).utf8String, -1, nil)
sqlite3_bind_int(statement, 2, Int32(note.id))


if sqlite3_step(statement) != SQLITE_DONE {
print("Could not update note")
}

sqlite3_finalize(statement)
}

func delete(note: Note) {
connect()
var statement: OpaquePointer!
if sqlite3_prepare_v2(database, "DELETE FROM notes WHERE rowid = ?", -1, &statement, nil) != SQLITE_OK {
print("Could not create delete statement")
}

sqlite3_bind_int(statement, 1, Int32(note.id))

if sqlite3_step(statement) != SQLITE_DONE {
print("Could not delete note")
}

sqlite3_finalize(statement)
}
}

文本列表界面

ViewController.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import UIKit

class ViewController: UITableViewController, UISearchBarDelegate {
var notes: [Note] = []
var notesBackup: [Note] = []

// 搜索框
@IBOutlet var searchBar: UISearchBar!
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText == "" {
notes = notesBackup
}
else {
notes = []
for note: Note in notesBackup {
if note.contents.lowercased().contains(searchText.lowercased()) {
notes.append(note)
}
}
}
tableView.reloadData()
}

//创建一条新笔记
@IBAction func createNote() {
let _ = NoteManager.main.create()
reload()
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
reload()
}

override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
}

override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notes.count
}

// 让每一个tableCell显示对应文本笔记的内容
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NoteCell", for: indexPath)
cell.textLabel?.text = notes[indexPath.row].contents
return cell
}

// 重载页面
func reload() {
notes = NoteManager.main.getAllNotes()
notesBackup = NoteManager.main.getAllNotes()
self.tableView.reloadData()
}

// 页面跳转时把对应笔记b写入新页面
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "NoteSegue" {
if let destination = segue.destination as? NoteViewController {
destination.note = notes[tableView.indexPathForSelectedRow!.row]
}
}
}
}

文本详情界面

NoteViewController.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import UIKit


// 文本记事的笔记详情页
class NoteViewController: UIViewController {
var note: Note!

@IBOutlet var textView: UITextView!

@IBAction func deleteNote() {
let _ = NoteManager.main.delete(note: note)
navigationController?.popViewController(animated: true)

}

// 载入已有的笔记数据
override func viewDidLoad() {
super.viewDidLoad()
textView.text = note.contents
}

// 保存返回时的笔记数据
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
note.contents = textView.text
NoteManager.main.save(note: note)
}
}

图文记事

数据类

Note+CoreDataClass.swift

(文件可自动生成,不过是空文件,代码需要自己添加。生成操作见Swift使用CoreData储存数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import UIKit
import CoreData

@objc(Note)
public class ImageNote: NSManagedObject {
var addDate: Date? {
get {
return rawAddDate as Date?
}
set {
rawAddDate = newValue as NSDate?
}
}

var image: UIImage? {
get {
if let imageData = rawImage as Data? {
return UIImage(data: imageData)
} else {
return nil
}
}
set {
if let image = newValue {
rawImage = convertImageToNSData(image: image)
}
}
}

convenience init?(title: String, body: String?, image: UIImage?) {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
// import UIKit is needed to access UIApplication

guard let managedContext = appDelegate?.persistentContainer.viewContext, !title.isEmpty else {
return nil
}

self.init(entity: ImageNote.entity(), insertInto: managedContext)
self.title = title
self.body = body
self.addDate = Date(timeIntervalSinceNow: 0)

if let image = image {
self.rawImage = convertImageToNSData(image: image)
}
}

func convertImageToNSData(image: UIImage) -> NSData? {
// The image data can be represented as PNG or JPEG data formats.
// Both ways to format the image data are listed below and the JPEG version is the one being used.

//return image.jpegData(compressionQuality: 1.0) as NSData?
return processImage(image: image).pngData() as NSData?
}

// See: https://stackoverflow.com/questions/3554244/uiimagepngrepresentation-issues-images-rotated-by-90-degrees/33311936
// This function processes the image so that it is oriented correctly when displayed.
func processImage(image: UIImage) -> UIImage {
if (image.imageOrientation == .up) {
return image
}

UIGraphicsBeginImageContext(image.size)

image.draw(in: CGRect(origin: CGPoint.zero, size: image.size), blendMode: .copy, alpha: 1.0)
let copy = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()

guard let unwrappedCopy = copy else {
return image
}

return unwrappedCopy
}
}

Note+CoreDataProperties.swift

(文件可自动生成,不过是空文件,代码需要自己添加。生成操作见Swift使用CoreData储存数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Foundation
import CoreData


extension ImageNote {

@nonobjc public class func fetchRequest() -> NSFetchRequest<ImageNote> {
return NSFetchRequest<ImageNote>(entityName: "Note")
}

@NSManaged public var title: String?
@NSManaged public var body: String?
@NSManaged public var rawAddDate: NSDate?
@NSManaged public var rawImage: NSData?

}

图文列表界面

NotesViewController.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import UIKit
import CoreData

class NotesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

var notes = [ImageNote]()
var dateFormatter = DateFormatter()
@IBOutlet weak var notesTableView: UITableView!

override func viewDidLoad() {
super.viewDidLoad()

dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .medium
}

override func viewWillAppear(_ animated: Bool) {
fetchNotes()
notesTableView.reloadData()
}

func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notes.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "noteCell", for: indexPath)

let note = notes[indexPath.row]
cell.textLabel?.text = note.title
if let addDate = note.addDate {
cell.detailTextLabel?.text = dateFormatter.string(from: addDate)
}

return cell
}

// 左划选择删除笔记
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (rowAction, indexPath) in
self.deleteNote(indexPath: indexPath)
}

return [deleteAction]
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Note" {
if let destination = segue.destination as? NoteDetailTableViewController {
destination.note = notes[notesTableView.indexPathForSelectedRow!.row]
}
}
}

// 取出存在本地的图文笔记数据
func fetchNotes() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
notes = [ImageNote]()
return
}

let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<ImageNote> = ImageNote.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "rawAddDate", ascending: true)]

do {
notes = try managedContext.fetch(fetchRequest)
} catch {
alertNotifyUser(message: "Fetch for notes failed.")
}
}


func deleteNote(indexPath: IndexPath) {
let note = notes[indexPath.row]

if let managedObjectContext = note.managedObjectContext {
managedObjectContext.delete(note)

do {
try managedObjectContext.save()
self.notes.remove(at: indexPath.row)
notesTableView.reloadData()
} catch {
alertNotifyUser(message: "Delete failed.")
notesTableView.reloadData()
}
}
}

func alertNotifyUser(message: String) {
let alert = UIAlertController(title: "Alert", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}

图文详情界面

NoteDetailTableViewController.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import UIKit

class NoteDetailTableViewController: UITableViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
@IBOutlet weak var titleTextField: UITextField!
@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var bodyTextView: UITextView!
@IBOutlet weak var imageView: UIImageView!

let dateFormatter = DateFormatter()
let newNoteDateFormatter = DateFormatter()
let imagePickerController = UIImagePickerController()

var note: ImageNote!
var image: UIImage!

override func viewDidLoad() {
super.viewDidLoad()

// Stylize the body Text View.
bodyTextView.layer.borderWidth = 1.0
bodyTextView.layer.borderColor = UIColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 1.0).cgColor
bodyTextView.layer.cornerRadius = 6.0

// Date Formatter for existing notes.
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .medium

// Separate Date Formatter for new notes that only shows date (and no time).
// The date and time are set when the note is saved in Core Data.
newNoteDateFormatter.dateStyle = .medium

// Initialize the form data.
// If existing note, display its information.
// If new note, show empty fields and a current Date (no time)

if let note = note {
titleTextField.text = note.title
bodyTextView.text = note.body
if let addDate = note.addDate {
dateLabel.text = dateFormatter.string(from: addDate)
}
image = note.image
imageView.image = image
} else {
titleTextField.text = ""
bodyTextView.text = ""
dateLabel.text = newNoteDateFormatter.string(from: Date(timeIntervalSinceNow: 0))
imageView.image = nil
}
}

// 获取图片
@IBAction func selectImage(_ sender: Any) {
selectImageSource()
}

// 选择拍摄或者选择本地图片
func selectImageSource() {
let alert = UIAlertController(title: "Select Image Source", message: nil, preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Camera", style: .default, handler: {
(alertAction) in
self.takePhotoUsingCamera()
}))
alert.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: {
(alertAction) in
self.selectPhotoFromLibrary()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}

// 调用相机
func takePhotoUsingCamera() {
if (!UIImagePickerController.isSourceTypeAvailable(.camera)) {
alertNotifyUser(message: "This device has no camera.")
return
}

imagePickerController.sourceType = .camera
imagePickerController.delegate = self
present(imagePickerController, animated: true)
}

// 从本地选择
func selectPhotoFromLibrary() {
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
present(imagePickerController, animated: true)
}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
defer {
imagePickerController.dismiss(animated: true, completion: nil)
}

guard let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else {
return
}
image = selectedImage
imageView.image = image
if let note = note {
note.image = selectedImage
}
}

func alertNotifyUser(message: String) {
let alert = UIAlertController(title: "Alert", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}

// 保存图文笔记信息到本地
@IBAction func save(_ sender: Any) {
guard let title = titleTextField.text?.trimmingCharacters(in: .whitespaces), !title.isEmpty else {
alertNotifyUser(message: "Please enter a title before saving the note.")
return
}

// if an existing note, update it
// otherwise, create a new note
if let note = note {
note.title = title
note.body = bodyTextView.text
note.image = image
// addDate is set when the Note is initialized
// for existing note, the addDate stays the same as initially set
} else {
note = ImageNote(title: title, body: bodyTextView.text, image: image)
}

// If a note exists, save it.
if let note = note {
do {
let managedContext = note.managedObjectContext
try managedContext?.save()
} catch {
alertNotifyUser(message: "The note could not be saved.")
}

} else {
alertNotifyUser(message: "The note could not be created.")
}

// Return to list of Notes.
navigationController?.popViewController(animated: true)
}
}

日期记事

日期列表界面

DataViewController.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import UIKit

class DataViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var expenses : [Expense] = []

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

@IBOutlet weak var DataTableView: UITableView!


override func viewDidLoad() {
super.viewDidLoad()
DataTableView.delegate = self
DataTableView.dataSource = self
navigationItem.title = "Clock Note"

}
override func viewWillAppear(_ animated: Bool) {
//get data from coredata
getData()
//reload table view
DataTableView.reloadData()
}
@IBAction func addExpenses(_ sender: Any) {
performSegue(withIdentifier: "ShowExpenses", sender: self)
}


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return expenses.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = DataTableView.dequeueReusableCell(withIdentifier: "expensesCell", for: indexPath) as! ExpensesCell

let expense = expenses[indexPath.row]
if let title = expense.name {
cell.titleLabel.text = title
}

cell.amountLabel.text = "\(expense.amount)"

if let date = expense.date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm a"
cell.dateLabel.text = dateFormatter.string(from: date)
}

return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//performSegue(withIdentifier: "ShowExpenses", sender: self)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func getData() {
do {
expenses = try context.fetch(Expense.fetchRequest())
}catch {
print("fetchingFailed")
}

}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let expense = expenses[indexPath.row]
context.delete(expense)
(UIApplication.shared.delegate as! AppDelegate).saveContext()

getData()
}
tableView.reloadData()

}
}

日期列表中的小条目

ExpensesCell.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
import UIKit

class ExpensesCell: UITableViewCell {

@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var amountLabel: UILabel!
@IBOutlet weak var dateLabel: UILabel!

override func awakeFromNib() {
super.awakeFromNib()

}
}

日期详情界面

AddDataViewController.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import UIKit

class AddDataViewController: UIViewController {

@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var amountTextField: UITextField!
@IBOutlet weak var datePicker: UIDatePicker!

override func viewDidLoad() {
super.viewDidLoad()

}


@IBAction func saveExpenses(_ sender: Any) {

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext


let expense = Expense(context: context)

if let name = nameTextField.text {
expense.name = name
}
if let amount = amountTextField.text {
expense.amount = (amount as NSString).doubleValue
}

expense.date = datePicker.date

(UIApplication.shared.delegate as! AppDelegate).saveContext()
navigationController?.popViewController(animated: true)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
}
隐藏