数据持久化
文件的保存和读取
Json文件:每个乐谱页对应一个Json文件。文件保存乐谱每小节的大小和位置信息,以及乐谱的基本信息,包括每小节节拍数和用户设置的速度和Mask偏移量。
png文件:每个乐谱都有一个乐谱图片,如果用户做了笔记,还有一个笔记图片。
保存文件的代码如下:
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
func getRootPath() -> String? {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
}
func saveImageFile() {
if let rootPath = Utility.getRootPath(),
let imageName = getFileName() {
let imagePath = "\(rootPath)/\(imageName).png"
print("image path: \(imagePath)")
if let image = imageView.image, let imageData = image.pngData() {
FileManager.default.createFile(atPath: imagePath, contents: imageData, attributes: nil)
}
}
}
func saveJsonFile() {
if let rootPath = Utility.getRootPath(),
let jsonFileName = getFileName() {
let jsonPath = "\(rootPath)/\(jsonFileName).json"
print("image path: \(jsonPath)")
let jsonDic: [String: Any] = [basicInfoKey: [String: String](), barFramesKey: barFrames]
if let jsonData = try? NSKeyedArchiver.archivedData(withRootObject: jsonDic, requiringSecureCoding: false) {
FileManager.default.createFile(atPath: jsonPath, contents: jsonData, attributes: nil)
}
}
}
读取文件的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func loadJsonFile() {
if let rootPath = Utility.getRootPath(),
let jsonName = navigationItem.title,
let jsonData = FileManager.default.contents(atPath: "\(rootPath)/\(jsonName).json"),
let jsonObjectAny = NSKeyedUnarchiver.unarchiveObject(with: jsonData),
let jsonObject = jsonObjectAny as? [String: Any] {
if let sheetBasicInfo = jsonObject[basicInfoKey] as? [String: String] {
self.sheetBasicInfo = sheetBasicInfo
}
if let barFrames = jsonObject[barFramesKey] as? [Int: CGRect] {
self.barFrames = barFrames
}
}
}
func loadSheetImage(with imageName: String) {
if let rootPath = Utility.getRootPath(),
let sheetImage = UIImage(contentsOfFile: "\(rootPath)/\(imageName).png") {
sheetImageView.image = sheetImage
noteImageView.image = UIImage(contentsOfFile: "\(rootPath)/\(imageName)\(noteImageSubfix).png")
layoutImageView()
}
}
NSDefault:保存标签信息
1
2
UserDefaults.standard.setValue(allTags, forKey: allTagsKey)
let allTags = UserDefaults.standard.value(forKey: allTagsKey)
参考
播放节拍器声音
通过AVFoundation播放声音
1
2
3
4
if let audioUrl = Bundle.main.url(forResource: "FirstMeter", withExtension: "wav", subdirectory: "Resource.bundle") {
AudioServicesCreateSystemSoundID(audioUrl as CFURL, &firstMeterId)
}
AudioServicesPlaySystemSound(self.firstMeterId)
节拍器音量调节
在设置中,如果“声音->铃声和警报”下面的“跟随按钮”没有打开,那么通过AudioServicesPlaySystemSound()
播放的声音就会始终用一个固定的音量播放,其它声音API播放的声音(比如AVAudioPlayer
)会跟随系统音量变化音量大小。
In Settings app, Sounds->RINGER AND ALERTS, if ‘Change with Buttons’ is set to Off, then sounds using AudioServicesPlaySystemSound() will always be played at a fixed volume (yet other sound API’s such as AVAudioPlayer will respect the volume of the device).
navigationController的使用和数据的传递
通过下面两种方法进行ViewController的弹出
1
2
3
let playVC = storyBoard.instantiateViewController(identifier: "Play")
playVC.navigationItem.title = filtedFileNames[indexPath.row]
navigationController?.pushViewController(playVC, animated: true)
或者
1
2
3
4
let colorPickerVC = UIColorPickerViewController()
colorPickerVC.selectedColor = brushColorButton.selectedColor
colorPickerVC.delegate = self
present(colorPickerVC, animated: true, completion: nil)
通过delegate进行目标ViewController到源ViewController的方法调用和数据传递,源ViewControler可以直接设置目标ViewController的属性进行数据传递。
参考
PhotoKit的使用
需要在info.plist里面设置NSPhotoLibraryUsageDescription
属性,设置在获取权限的时候显示给用户的弹窗中显示的内容。
检查权限状态:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private func requestPrivilegeAndLoadPhotos() {
let status = PHPhotoLibrary.authorizationStatus()
if status == .authorized {
loadPhotos()
} else {
PHPhotoLibrary.requestAuthorization { (status) in
if status == .authorized {
self.loadPhotos()
DispatchQueue.main.async {
self.collection.reloadData()
}
} else {
// use not grant the privilege
}
}
}
}
如果用户给了访问相册的权限,通过下面的方法加载所有图片信息:
1
2
3
4
5
private func loadPhotos() {
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
allPhotos = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)
}
通过下面方法将所有图片信息显示在collection列表中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allPhotos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collection.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? PhotoCollectionCell {
let assert = allPhotos.object(at: indexPath.item)
PHImageManager.default().requestImage(for: assert, targetSize: CGSize(width: photoCollectionWH, height: photoCollectionWH), contentMode: .aspectFill, options: .none) { (image, dic) in
if let image = image {
cell.imageView.image = image
}
}
return cell
} else {
return collection.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
}
}
通过下面方法,将用户选择的图片传给delegate处理(delegate可以是源ViewController):
1
2
3
4
5
6
7
8
9
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("item: \(indexPath.item)")
let assert = allPhotos.object(at: indexPath.item)
PHImageManager.default().requestImage(for: assert, targetSize: CGSize(width: assert.pixelWidth, height: assert.pixelHeight), contentMode: .aspectFill, options: .none) { (image, dic) in
if let image = image {
self.delegate?.set(image: image, and: nil)
}
}
}
参考
Alert View
Displaying Alerts with UIAlertController in Swift
UIImage
下面两个图片加载方法对cache的运用是不一样的:
1
2
+ (UIImage *)imageNamed:(NSString *)name: use cached images
+ (UIImage *)imageWithContentsOfFile:(NSString *)path: skip cached images and read data directly from file
参考
iOS UIImage Cache
可编辑的UITableView
通过实现下面方法保证每个cell支持左滑操作:
1
2
3
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
通过实现下面的方法,左滑之后显示两个按钮: Edit Tags和Delete
1
2
3
4
5
6
7
8
9
10
11
12
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
self.deleteItem(at: indexPath)
}
let editAction = UITableViewRowAction(style: .default, title: "Edit Tags") { (action, indexPath) in
self.editTags(for: indexPath)
}
editAction.backgroundColor = UIColor(displayP3Red: 60/255, green: 148/255, blue: 1.0, alpha: 1.0)
deleteAction.backgroundColor = .red
return [deleteAction, editAction]
}
CALayer & CAShapeLayer & Core Graphics
这一块的内容太多,希望有时间可以单独总结一下。
音乐术语英语
音乐术语英文名称汇总
swift中构造方法designated,convenience
官方文档中有如下描述:
子类designated构造方法中必须调用父类的designated构造方法。
convenience构造方法中必须调用当前类的构造方法。
convenience构造方法归根结底要调用到designated构造方法。
参考
笔记绘制功能
通过第三方库STSketchKit 实现笔记功能支持Undo/Redo操作。官方介绍如下:
ATSketchKit is a drawing / sketching framework for iOS written in Swift.
It can be used as the foundation for an artistic app, a simple signature feature or more inteligent graph designing app.