├── screenshots
└── screenshoot.gif
├── VoiceMemo
├── Supporting Files
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── list.imageset
│ │ │ ├── Untitled.png
│ │ │ ├── Untitled@2x.png
│ │ │ ├── Untitled@3x.png
│ │ │ └── Contents.json
│ │ ├── play_begin.imageset
│ │ │ ├── Untitled.png
│ │ │ ├── Untitled@2x.png
│ │ │ ├── Untitled@3x.png
│ │ │ └── Contents.json
│ │ ├── play_pause.imageset
│ │ │ ├── Untitled.png
│ │ │ ├── Untitled@2x.png
│ │ │ ├── Untitled@3x.png
│ │ │ └── Contents.json
│ │ ├── record_begin.imageset
│ │ │ ├── Untitled.png
│ │ │ ├── Untitled@2x.png
│ │ │ ├── Untitled@3x.png
│ │ │ └── Contents.json
│ │ ├── record_pause.imageset
│ │ │ ├── Untitled.png
│ │ │ ├── Untitled@2x.png
│ │ │ ├── Untitled@3x.png
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Info.plist
│ └── Base.lproj
│ │ └── LaunchScreen.storyboard
├── Data
│ ├── DateHandler.swift
│ ├── VoiceMemo.xcdatamodeld
│ │ └── VoiceMemo.xcdatamodel
│ │ │ └── contents
│ ├── Voice.swift
│ ├── FileHandler.swift
│ └── CoreDataStack.swift
├── Utils
│ ├── Utils.swift
│ ├── AudioSessionHelper.swift
│ ├── Alert.swift
│ └── SegueHandlerType.swift
├── Recording
│ ├── RecordingViewController+Playing.swift
│ ├── RecordingViewController+Recording.swift
│ ├── RecordingManager.swift
│ └── RecordingViewController.swift
├── Player
│ └── PlayManager.swift
├── AudioList
│ ├── CircularProgressView.swift
│ └── VoiceListViewController.swift
└── Base.lproj
│ └── Main.storyboard
├── VoiceMemo.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── .gitignore
├── VoiceMemoTests
└── Supporting Files
│ └── Info.plist
├── VoiceMemoUITests
└── Supporting Files
│ └── Info.plist
└── README.md
/screenshots/screenshoot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/screenshots/screenshoot.gif
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/list.imageset/Untitled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/list.imageset/Untitled.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/list.imageset/Untitled@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/list.imageset/Untitled@2x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/list.imageset/Untitled@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/list.imageset/Untitled@3x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/play_begin.imageset/Untitled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/play_begin.imageset/Untitled.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/play_pause.imageset/Untitled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/play_pause.imageset/Untitled.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/play_begin.imageset/Untitled@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/play_begin.imageset/Untitled@2x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/play_begin.imageset/Untitled@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/play_begin.imageset/Untitled@3x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/play_pause.imageset/Untitled@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/play_pause.imageset/Untitled@2x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/play_pause.imageset/Untitled@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/play_pause.imageset/Untitled@3x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/record_begin.imageset/Untitled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/record_begin.imageset/Untitled.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/record_pause.imageset/Untitled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/record_pause.imageset/Untitled.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/record_begin.imageset/Untitled@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/record_begin.imageset/Untitled@2x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/record_begin.imageset/Untitled@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/record_begin.imageset/Untitled@3x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/record_pause.imageset/Untitled@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/record_pause.imageset/Untitled@2x.png
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/record_pause.imageset/Untitled@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shannon-s-Dreamland/VoiceMemo/HEAD/VoiceMemo/Supporting Files/Assets.xcassets/record_pause.imageset/Untitled@3x.png
--------------------------------------------------------------------------------
/VoiceMemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 |
3 | ## Build generated
4 | build/*
5 | DerivedData
6 |
7 | ## Various settings
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 |
18 | ## Other
19 | *.xccheckout
20 | *.moved-aside
21 | *.xcuserstate
22 | *.xcscmblueprint
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 |
28 | # AppCode
29 | .idea/
30 |
31 | # Finder
32 | .DS_Store
33 |
34 | # Jazzy
35 | docs/
36 |
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/list.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Untitled.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Untitled@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Untitled@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/play_begin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Untitled.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Untitled@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Untitled@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/play_pause.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Untitled.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Untitled@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Untitled@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/record_begin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Untitled.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Untitled@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Untitled@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/record_pause.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Untitled.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Untitled@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Untitled@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/25/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
18 | return true
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/VoiceMemo/Data/DateHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateHandler.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSDate {
12 | var stringValue: String {
13 | let dateFormatter: NSDateFormatter = {
14 | let formatter = NSDateFormatter()
15 | formatter.dateStyle = .ShortStyle
16 | formatter.timeStyle = .ShortStyle
17 | return formatter
18 | }()
19 |
20 | return dateFormatter.stringFromDate(self)
21 | }
22 | }
--------------------------------------------------------------------------------
/VoiceMemo/Utils/Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Utils.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Double {
12 | func format(f: Int) -> String {
13 | return NSString(format: "%.\(f)f", self) as String
14 | }
15 | }
16 |
17 | extension String {
18 | /**
19 | 将 String 用指定的 Separator 切割成 String 数组
20 |
21 | - parameter separator: 要用的 Separator, 默认为空格
22 |
23 | - returns: 一个切割好的 String 数组
24 | */
25 | func segmentsWithSeparator(separator: Character = " ") -> [String] {
26 | return self.characters.split(separator).map(String.init)
27 | }
28 | }
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/VoiceMemoTests/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/VoiceMemoUITests/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/VoiceMemo/Data/VoiceMemo.xcdatamodeld/VoiceMemo.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/VoiceMemo/Data/Voice.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Voice+CoreDataProperties.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 | // Choose "Create NSManagedObject Subclass…" from the Core Data editor menu
9 | // to delete and recreate this implementation file for your updated model.
10 | //
11 |
12 | import Foundation
13 | import CoreData
14 |
15 | class Voice: NSManagedObject {
16 |
17 | @NSManaged var name: String
18 | @NSManaged var date: NSDate
19 | @NSManaged var duration: Double
20 |
21 |
22 | func fetchPathURL() -> NSURL? {
23 | do {
24 | let URL = try FileHandler.getDirectoryURL().URLByAppendingPathComponent(name)
25 | return URL
26 | } catch {
27 | assertionFailure()
28 | return nil
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/VoiceMemo/Data/FileHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AudioFileHandler.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum FileHandlerError: ErrorType {
12 | case DirectoryCreationError
13 | }
14 |
15 | struct FileHandler {
16 | static func getDirectoryURL() throws -> NSURL {
17 | let doucumentURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
18 |
19 | let directoryURL = doucumentURL.URLByAppendingPathComponent("Voice")
20 | do {
21 | try NSFileManager.defaultManager().createDirectoryAtURL(directoryURL, withIntermediateDirectories: true, attributes: nil)
22 | } catch {
23 | debugPrint("无法创建磁盘目录: \(error)")
24 | throw FileHandlerError.DirectoryCreationError
25 | }
26 | return directoryURL
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/VoiceMemo/Utils/AudioSessionHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AudioSessionHelper.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import AVFoundation
10 |
11 | class AudioSessionHelper {
12 |
13 | class func setupSessionActive(active: Bool, catagory: String = AVAudioSessionCategoryPlayback) {
14 | let session = AVAudioSession.sharedInstance()
15 | if active {
16 | do {
17 | try session.setCategory(catagory)
18 | } catch {
19 | assertionFailure()
20 | }
21 |
22 | do {
23 | try session.setActive(true)
24 | } catch {
25 | assertionFailure()
26 | }
27 | } else {
28 | do {
29 | try session.setActive(false, withOptions: .NotifyOthersOnDeactivation)
30 | } catch {
31 | assertionFailure()
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # 说明
3 |
4 | 
5 |
6 | 这是一个使用 AVAudioRecorder和 AVAudioPlayer 配合 Core Data 写的一个简单的音频录制和播放 Demo, 虽然界面比较粗糙, 而且因为时间问题, 应该会有点小 Bug, 但是代码和文件的管理应该能够达到生产水平, 所以修改 Bug 会十分容易!
7 |
8 | 我现在正计划写 iOS 开发的两个专题, 分别是关于36氪4.0版本的程序架构和用面向对象的方法做UI开发, 这个示例属于后者, 你可以在[这个网址](http://www.jianshu.com/users/61cd67a3e447/latest_articles)找到所有的文章, 很遗憾, 因为我们4.0的开发任务比较繁重,尽管我最近经常晚上写代码到凌晨, 但是还是 一直没有时间总结这些东西, 我是计划春节后将这两个专题的文章都完成, 算是在36氪工作半年来的一个总结. 希望回家后我的小侄子不会缠着我吧!
9 |
10 | 你可以随时关注上面的博客地址来获取最新的更新.
11 |
12 | ## 关于这个示例
13 |
14 | 这个示例不是最终版本, 从程序的大概样式也可以看出. 因为我们的客户端没有音频功能, 所以对这一块并不能说很系统的把握, 特别是 Core Data, 工作之后就没有再用过, 很多知识都是凭借模糊的记忆, 以及翻看苹果资料 Github 别人的代码书写的, 唯一可以保证的是整个的设计开发流程是自己的, 而且还是蛮不错的, 特别是对状态的管理, 最近使用 AVFoudation, 可能最大的感触就是相比较简单的界面, 里面的状态更多, 更不易管理, 使用程序中的状态机的原理是我找到的比较好的管理方案.
15 |
16 | 计划过年前的一个多月借助苹果的文档和 WWDC 的视频对这一块有一个系统的认识, 系统我可以找到一个助手 UI, 将这个 demo 逐步的完善成一个优秀的 demo, 哈哈, 好吧, 这确实是一个 demo, 只是不喜欢制造垃圾.
17 |
18 | 年底前还有一个计划就是系统的了解 AutoLayout, 打算写一个自己用的库, 实在不喜欢 SnapKit 的前缀语法, 你可以查看 [DropdownMenu](https://inatu@github.com/Shannon-s-Dreamland/DropdownMenu.git)里面的对 UIView 的扩展来了解一下大概的想法.
19 |
--------------------------------------------------------------------------------
/VoiceMemo/Recording /RecordingViewController+Playing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordingViewController+Playing.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension RecordingViewController {
12 | func updatePlayStatusWithCurrentTime(currentTime: NSTimeInterval, totalTime: NSTimeInterval) {
13 | timeLabel.text = currentTime.format(2) + "s/" + totalTime.format(2) + "s"
14 | }
15 |
16 | func playerDidChangeToState(state: PlaybackState) {
17 | switch state {
18 | case .Initial,
19 | .Finished:
20 | self.state = .RecordingFinished
21 | case .Pause,
22 | .Play:()
23 | }
24 | }
25 |
26 | func handlePlaybackError() {
27 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
28 | self.state = .RecordingFinished
29 | })
30 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("播放错误, 请重试", comment: ""), message: nil, actionTuples: [cancel])
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/VoiceMemo/Supporting Files/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/VoiceMemo/Utils/Alert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlertManager.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | typealias AlertHandler = ((UIAlertAction) -> Void)?
12 | typealias AlertActionTuple = (title: String, style: UIAlertActionStyle, handler: AlertHandler)
13 |
14 | protocol TextFieldAlertDelegate {
15 | func inputFinishedWithContent(content: String?)
16 | }
17 |
18 | struct Alert {
19 | private static func actionsFromTuple(tuple: [AlertActionTuple]) -> [UIAlertAction] {
20 | var actions = [UIAlertAction]()
21 |
22 | tuple.forEach { (title, style, handler) -> () in
23 | actions.append(UIAlertAction(title: title, style: style, handler: handler))
24 | }
25 |
26 | return actions
27 | }
28 |
29 | static func showAlertWithSourceViewController(sourceVC: UIViewController, title: String?, message: String?, actionTuples: [AlertActionTuple]) {
30 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
31 | actionsFromTuple(actionTuples).forEach { (action) -> () in
32 | alertController.addAction(action)
33 | }
34 |
35 | sourceVC.presentViewController(alertController, animated: true, completion: nil)
36 | }
37 |
38 | static func showInputAlertWithSourceViewController(sourceVC: UIViewController, title: String?, message: String?, confirmTitle: String?, delegate: TextFieldAlertDelegate) {
39 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
40 | alertController.addTextFieldWithConfigurationHandler(nil)
41 |
42 | let confirmAction = UIAlertAction(title: confirmTitle, style: .Default) { _ in
43 | delegate.inputFinishedWithContent(alertController.textFields?.first?.text)
44 | }
45 | alertController.addAction(confirmAction)
46 | sourceVC.presentViewController(alertController, animated: true, completion: nil)
47 |
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/VoiceMemo/Utils/SegueHandlerType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // Client
4 | //
5 | // Created by Shannon Wu on 10/25/15.
6 | // Copyright © 2015 36Kr. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 | /// StoryboardSegue 是 UIStoryboardSegue 的别名
12 | typealias StoryboardSegue = UIStoryboardSegue
13 | /// ViewController 是 UIViewController 的别名
14 | typealias ViewController = UIViewController
15 | #elseif os(OSX)
16 | import Cocoa
17 | /// StoryboardSegue 是 NSStoryboardSegue 的别名
18 | typealias StoryboardSegue = NSStoryboardSegue
19 | /// ViewController 是 NSWindowController 的别名
20 | typealias ViewController = NSWindowController
21 | #endif
22 |
23 | /**
24 | * 添加 `SegueHandlerType` 协议, 只有 `ViewController` 才可以继承这个协议, 并且限定
25 | `SegueIdentifier` 属性的 `RawValue` 必须是 `String` 类型
26 | */
27 | protocol SegueHandlerType {
28 |
29 | typealias SegueIdentifier: RawRepresentable
30 |
31 | }
32 |
33 | // MARK: - 添加 `SegueHandlerType` 协议, 只有 `ViewController` 才可以继承这个
34 | // 协议,并且限定 `SegueIdentifier` 属性的 `RawValue` 必须是 `String` 类型
35 | extension SegueHandlerType where Self: ViewController, SegueIdentifier.RawValue == String {
36 |
37 | /**
38 | 覆写 `UIViewController` 的 `performSegueWithIdentifier(_:sender:)` 方法,
39 | 但是接受的数据类型是 `segueIdentifier` 枚举类型, 而不是 `String` 类型
40 |
41 | - parameter segueIdentifier: 触发 `Segue` 的 `SegueIdentifier`
42 | - parameter sender: 触发 `Segue` 的发送者
43 | */
44 | func performSegueWithIdentifier(segueIdentifier: SegueIdentifier, sender: AnyObject?) {
45 | performSegueWithIdentifier(segueIdentifier.rawValue, sender: sender)
46 | }
47 |
48 | /**
49 | 通过 `Segue` 实例获取 `SegueIdentifier` 的 enum 实例, 如果 SegueIdentifier 中没有 enum 实例,
50 | 说明发生错误, 触发 fatalError
51 |
52 | - parameter segue: 要获取 `SegueIdentifier` 的 `Segue` 实例
53 |
54 | - returns: `SegueIdentifier` 的一个 enum 实例, 或者触发 fatalError
55 | */
56 | func segueIdentifierForSegue(segue: StoryboardSegue) -> SegueIdentifier {
57 | guard let identifier = segue.identifier,
58 | segueIdentifier = SegueIdentifier(rawValue: identifier) else {
59 | fatalError("在 \(self.dynamicType) 中, 无法通过 \(segue.identifier) 获取 SegueIdentifier 实例.")
60 | }
61 |
62 | return segueIdentifier
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/VoiceMemo/Recording /RecordingViewController+Recording.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordingViewController+ErrorHandling.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension RecordingViewController: RecordingManagerDelegate {
12 |
13 | func updateRecordedTimeInterval(duration: NSTimeInterval) {
14 | timeLabel.text = duration.format(0) + " s"
15 | }
16 |
17 | func handleRecordingInterrupted() {
18 | self.state = .Initial
19 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
20 | //
21 | })
22 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("录制过程发生中断, 请重新录制", comment: ""), message: nil, actionTuples: [cancel])
23 |
24 | }
25 |
26 | func hanldePermissionError() {
27 | self.state = .Initial
28 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
29 | //
30 | })
31 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("无法访问您设备的音频, 请在个人隐私中开启", comment: ""), message: nil, actionTuples: [cancel])
32 | }
33 |
34 | func handleRecorderFetchError() {
35 | self.state = .Initial
36 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
37 | //
38 | })
39 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("当前无法进行音频录制, 请稍后重试", comment: ""), message: nil, actionTuples: [cancel])
40 | }
41 |
42 | func handleRecorderEncodingError() {
43 | self.state = .Initial
44 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
45 | //
46 | })
47 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("音频编码失败, 请稍后重试", comment: ""), message: nil, actionTuples: [cancel])
48 | }
49 |
50 | func handleVoiceSaveError() {
51 | self.state = .RecordingFinished
52 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
53 | //
54 | })
55 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("文件保存失败, 请尝试重新命名", comment: ""), message: nil, actionTuples: [cancel])
56 | }
57 | }
--------------------------------------------------------------------------------
/VoiceMemo/Data/CoreDataStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataCenter.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/25/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | enum CoreDataStackError: ErrorType {
13 | case CannotCreateObject
14 | }
15 |
16 | final class CoreDataStack {
17 | // MARK: - Singleton
18 |
19 | private class var manager:CoreDataStack {
20 | struct SingletonWrapper {
21 | static let singleton = CoreDataStack()
22 | }
23 | return SingletonWrapper.singleton
24 | }
25 |
26 | static func save() {
27 | manager.saveContext()
28 | }
29 |
30 | static func createNewObject() throws -> T {
31 | if let object = NSEntityDescription.insertNewObjectForEntityForName("Voice", inManagedObjectContext: manager.managedObjectContext) as? T {
32 | return object
33 | } else {
34 | debugPrint("创建Voice失败")
35 | throw CoreDataStackError.CannotCreateObject
36 | }
37 | }
38 |
39 | static var context: NSManagedObjectContext {
40 | return manager.managedObjectContext
41 | }
42 |
43 | // MARK: - Initializers
44 |
45 | private init() {}
46 |
47 | // MARK: - Core Data stack
48 |
49 | private lazy var applicationDocumentsDirectory: NSURL = {
50 | let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
51 | return urls[urls.count-1]
52 | }()
53 |
54 | private lazy var managedObjectModel: NSManagedObjectModel = {
55 | let modelURL = NSBundle.mainBundle().URLForResource("VoiceMemo", withExtension: "momd")!
56 | return NSManagedObjectModel(contentsOfURL: modelURL)!
57 | }()
58 |
59 | private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
60 | let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
61 | let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
62 | var failureReason = "There was an error creating or loading the application's saved data."
63 | do {
64 | try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
65 | } catch {
66 | var dict = [String: AnyObject]()
67 | dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
68 | dict[NSLocalizedFailureReasonErrorKey] = failureReason
69 |
70 | dict[NSUnderlyingErrorKey] = error as NSError
71 | let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
72 | NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
73 | abort()
74 | }
75 |
76 | return coordinator
77 | }()
78 |
79 | private lazy var managedObjectContext: NSManagedObjectContext = {
80 | let coordinator = self.persistentStoreCoordinator
81 | var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
82 | managedObjectContext.persistentStoreCoordinator = coordinator
83 | return managedObjectContext
84 | }()
85 |
86 | private func saveContext () {
87 | if managedObjectContext.hasChanges {
88 | do {
89 | try managedObjectContext.save()
90 | } catch {
91 | let nserror = error as NSError
92 | NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
93 | abort()
94 | }
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/VoiceMemo/Player/PlayManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AudioPlayer.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AVFoundation
11 |
12 | // MARK: - PlayManagerDelegate
13 |
14 | protocol PlayManagerDelegate: class {
15 | func updatePlayStatusWithCurrentTime(currentTime: NSTimeInterval, totalTime: NSTimeInterval)
16 | func playerDidChangeToState(state: PlaybackState)
17 | func handlePlaybackError()
18 | }
19 |
20 | // MARK: - Playback State
21 |
22 | enum PlaybackState {
23 | case Initial
24 | case Play
25 | case Pause
26 | case Finished
27 | }
28 |
29 | // MARK: - Play Manager
30 |
31 | class PlayManager: NSObject {
32 |
33 | // MARK: Properties
34 |
35 | private var player: AVAudioPlayer?
36 |
37 | private var playTimer: NSTimer? {
38 | willSet {
39 | if newValue == nil {
40 | playTimer?.invalidate()
41 | }
42 | }
43 | }
44 |
45 | private var state: PlaybackState = .Initial {
46 | didSet {
47 | switch state {
48 | case .Initial,
49 | .Finished:
50 | player = nil
51 | playTimer = nil
52 | currentItem = nil
53 | AudioSessionHelper.setupSessionActive(false)
54 | case .Play:
55 | playTimer = nil
56 | playTimer = NSTimer(
57 | timeInterval: 0.1,
58 | target: self,
59 | selector: "updateProgress",
60 | userInfo: nil,
61 | repeats: true)
62 | NSRunLoop.currentRunLoop().addTimer(playTimer!, forMode: NSRunLoopCommonModes)
63 |
64 | if player != nil {
65 | player?.play()
66 | return
67 | }
68 |
69 | do {
70 | guard currentItem != nil else {
71 | assertionFailure()
72 | state = .Initial
73 | return
74 | }
75 | try player = AVAudioPlayer(contentsOfURL: currentItem!)
76 | player?.delegate = self
77 |
78 | AudioSessionHelper.setupSessionActive(true)
79 | player?.play()
80 | } catch {
81 | state = .Initial
82 | delegate?.handlePlaybackError()
83 | }
84 | case .Pause:
85 | playTimer = nil
86 | player?.pause()
87 | AudioSessionHelper.setupSessionActive(false)
88 | }
89 | delegate?.playerDidChangeToState(state)
90 | }
91 | }
92 |
93 | private var currentItem: NSURL? {
94 | didSet {
95 | guard currentItem != nil else { return }
96 | state = .Play
97 | }
98 | }
99 |
100 | weak var delegate: PlayManagerDelegate?
101 |
102 | // MARK: Initializers
103 |
104 | init(delegate: PlayManagerDelegate?) {
105 | super.init()
106 |
107 | self.delegate = delegate
108 | }
109 |
110 | // MARK: Convience
111 |
112 | func playItem(item: NSURL) {
113 | state = .Initial
114 |
115 | currentItem = item
116 | }
117 |
118 | func stop() {
119 | state = .Initial
120 | }
121 |
122 | func playingVoice(voice: Voice) -> Bool {
123 | return voice.name == currentItem?.lastPathComponent
124 | }
125 |
126 | func togglePlayState() {
127 | if state == .Play {
128 | state = .Pause
129 | } else if state == .Pause {
130 | state = .Play
131 | }
132 | }
133 |
134 | func updateProgress() {
135 | if let currentTime = player?.currentTime,
136 | totalTime = player?.duration {
137 | delegate?.updatePlayStatusWithCurrentTime(currentTime, totalTime: totalTime)
138 | }
139 | }
140 |
141 | }
142 |
143 | // MARK: - AVAudioPlayerDelegate
144 |
145 | extension PlayManager: AVAudioPlayerDelegate {
146 |
147 | func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
148 | if flag {
149 | state = .Finished
150 | }
151 | }
152 |
153 | func audioPlayerDecodeErrorDidOccur(player: AVAudioPlayer, error: NSError?) {
154 | debugPrint(error)
155 |
156 | delegate?.handlePlaybackError()
157 | }
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/VoiceMemo/AudioList/CircularProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CircularProgressView.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @IBDesignable
12 | class CircularProgressView: UIView {
13 | // MARK: Types
14 |
15 | enum Style {
16 | case Play
17 | case Pause
18 | case Stop
19 |
20 | func path(layerBounds: CGRect) -> CGPath {
21 | switch self {
22 | case .Play:
23 | let path = UIBezierPath()
24 | path.moveToPoint(CGPoint(x: layerBounds.width / 5, y: 0))
25 | path.addLineToPoint(CGPoint(x: layerBounds.width, y: layerBounds.height / 2))
26 | path.addLineToPoint(CGPoint(x: layerBounds.width / 5, y: layerBounds.height))
27 | path.closePath()
28 | return path.CGPath
29 | case .Pause:
30 | var rect = CGRect(origin: CGPoint(x: layerBounds.width * 0.1, y: 0), size: CGSize(width: layerBounds.width * 0.2, height: layerBounds.height))
31 | let path = UIBezierPath(rect: rect)
32 | rect.offsetInPlace(dx: layerBounds.width * 0.6, dy: 0)
33 | path.appendPath(UIBezierPath(rect: rect))
34 | return path.CGPath
35 | case .Stop:
36 | let insetBounds = CGRectInset(layerBounds, layerBounds.width / 6, layerBounds.width / 6)
37 | let path = UIBezierPath(rect: insetBounds)
38 | return path.CGPath
39 | }
40 | }
41 | }
42 |
43 | // MARK: Properties
44 |
45 | lazy var backgroundLayer: CAShapeLayer = {
46 | let layer = CAShapeLayer()
47 | layer.fillColor = nil
48 | layer.lineWidth = self.lineWidth
49 | layer.strokeColor = self.backgroundLayerStrokeColor.CGColor
50 | self.layer.addSublayer(layer)
51 |
52 | return layer
53 | }()
54 |
55 | lazy var progressLayer: CAShapeLayer = {
56 | let layer = CAShapeLayer()
57 | layer.fillColor = nil
58 | layer.lineWidth = self.lineWidth
59 | layer.strokeColor = self.tintColor.CGColor
60 | self.layer.insertSublayer(layer, above: self.backgroundLayer)
61 |
62 | return layer
63 | }()
64 |
65 | lazy var iconLayer: CAShapeLayer = {
66 | let layer = CAShapeLayer()
67 | layer.fillColor = self.tintColor.CGColor
68 | self.layer.addSublayer(layer)
69 |
70 | return layer
71 | }()
72 |
73 | var iconStyle: Style = .Play {
74 | didSet {
75 | iconLayer.path = iconStyle.path(iconLayer.bounds)
76 | }
77 | }
78 |
79 |
80 | var iconLayerBounds: CGRect {
81 | return iconLayer.bounds
82 | }
83 |
84 | @IBInspectable var lineWidth: CGFloat = 3 {
85 | didSet {
86 | backgroundLayer.lineWidth = lineWidth
87 | progressLayer.lineWidth = lineWidth
88 | }
89 | }
90 |
91 | @IBInspectable var iconLayerFrameRatio: CGFloat = 0.4 {
92 | didSet {
93 | iconLayer.frame = iconLayerFrame(iconLayerBounds, ratio: iconLayerFrameRatio)
94 | iconLayer.path = iconStyle.path(iconLayerBounds)
95 | }
96 | }
97 |
98 | @IBInspectable var backgroundLayerStrokeColor: UIColor = UIColor(white: 0.8, alpha: 1) {
99 | didSet {
100 | backgroundLayer.strokeColor = backgroundLayerStrokeColor.CGColor
101 | }
102 | }
103 |
104 | @IBInspectable var progressLayerStrokeColor: UIColor? {
105 | didSet {
106 | progressLayer.strokeColor = progressLayerStrokeColor?.CGColor ?? tintColor.CGColor
107 | }
108 | }
109 |
110 | @IBInspectable var progress: CGFloat = 0.0 {
111 | didSet {
112 | progressLayer.strokeEnd = progress
113 | }
114 | }
115 |
116 | // MARK: Functions
117 |
118 | func setProgress(progress: CGFloat, animated: Bool = true) {
119 | if animated {
120 | self.progress = progress
121 | } else {
122 | self.progress = progress
123 | let animation = CABasicAnimation(keyPath: "strokeEnd")
124 | animation.fromValue = progress
125 | animation.duration = 0.0
126 | progressLayer.addAnimation(animation, forKey: nil)
127 | }
128 | }
129 |
130 | func iconLayerFrame(rect: CGRect, ratio: CGFloat) -> CGRect {
131 | let insetRatio = (1 - ratio) / 2.0
132 | return CGRectInset(rect, CGRectGetWidth(rect) * insetRatio, CGRectGetHeight (rect) * insetRatio)
133 | }
134 |
135 | func getSquareLayerFrame(rect: CGRect) -> CGRect {
136 | if rect.width != rect.height {
137 | let width = min(rect.width, rect.height)
138 |
139 | let originX = (rect.width - width) / 2
140 | let originY = (rect.height - width) / 2
141 |
142 | return CGRectMake(originX, originY, width, width)
143 | }
144 | return rect
145 | }
146 |
147 | override func layoutSubviews() {
148 | super.layoutSubviews()
149 | let squareRect = getSquareLayerFrame(layer.bounds)
150 | backgroundLayer.frame = squareRect
151 | progressLayer.frame = squareRect
152 |
153 | let innerRect = CGRectInset(squareRect, lineWidth / 2.0, lineWidth / 2.0)
154 | iconLayer.frame = iconLayerFrame(innerRect, ratio: iconLayerFrameRatio)
155 |
156 | let center = CGPointMake(squareRect.width / 2.0, squareRect.height / 2.0)
157 | let path = UIBezierPath(arcCenter: center, radius: innerRect.width / 2.0, startAngle: CGFloat(-M_PI_2), endAngle: CGFloat(-M_PI_2 + 2.0 * M_PI), clockwise: true)
158 | backgroundLayer.path = path.CGPath
159 | progressLayer.path = path.CGPath
160 | iconLayer.path = iconStyle.path(iconLayerBounds)
161 | }
162 |
163 | override func prepareForInterfaceBuilder() {
164 | super.prepareForInterfaceBuilder()
165 |
166 | iconStyle = .Play
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/VoiceMemo/Recording /RecordingManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordingManager.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/26/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AVFoundation
11 |
12 | // MARK: - RecordingManagerDelegate
13 |
14 | protocol RecordingManagerDelegate: class {
15 | func updateRecordedTimeInterval(duration: NSTimeInterval)
16 | func handleRecordingInterrupted()
17 | func hanldePermissionError()
18 | func handleRecorderFetchError()
19 | func handleRecorderEncodingError()
20 | func handleVoiceSaveError()
21 | }
22 |
23 | // MARK: - RecordingError
24 |
25 | enum RecordingError: ErrorType {
26 | case AudioExist
27 | case RecorderNotExist
28 | }
29 |
30 | // MARK: RecordingState
31 |
32 | enum RecordingState {
33 | case Initial
34 | case Finished
35 | }
36 |
37 | // MARK: - RecordingManager
38 |
39 | class RecordingManager:NSObject, AVAudioRecorderDelegate {
40 | // MARK: Properties
41 |
42 | weak var delegate: RecordingManagerDelegate?
43 | var audioDuration: NSTimeInterval {
44 | return recordDuration
45 | }
46 | private var state = RecordingState.Initial
47 | private var audioRecorder: AVAudioRecorder?
48 | private var recordTimer: NSTimer? {
49 | willSet {
50 | if newValue == nil {
51 | recordTimer?.invalidate()
52 | }
53 | }
54 | }
55 | private var recordDuration: NSTimeInterval = 0
56 | let tmpStoreURL = NSURL.fileURLWithPath(NSTemporaryDirectory()).URLByAppendingPathComponent("tmpVoice.caf")
57 |
58 | // MARK: Initializers
59 | init(delegate: RecordingManagerDelegate?) {
60 | super.init()
61 |
62 | self.delegate = delegate
63 | prepareForRecording()
64 | }
65 |
66 | // MARK: Recording Management
67 |
68 | func startNewRecording() throws {
69 | guard let recorder = self.audioRecorder else { throw RecordingError.RecorderNotExist }
70 | guard state != .Finished else { throw RecordingError.AudioExist }
71 |
72 | AudioSessionHelper.setupSessionActive(true, catagory: AVAudioSessionCategoryRecord)
73 | if recorder.prepareToRecord() {
74 | debugPrint("Start recording")
75 |
76 | audioRecorder?.record()
77 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleInterruption:", name: AVAudioSessionInterruptionNotification, object: AVAudioSession.sharedInstance())
78 |
79 | self.recordTimer = NSTimer.scheduledTimerWithTimeInterval(1.0,
80 | target: self,
81 | selector: "updateDuration",
82 | userInfo: nil,
83 | repeats: true)
84 | }
85 | }
86 |
87 | func finishRecording() {
88 | recordTimer = nil
89 | state = .Finished
90 | recordDuration = audioRecorder?.currentTime ?? 0
91 | audioRecorder?.stop()
92 | AudioSessionHelper.setupSessionActive(false)
93 | }
94 |
95 | func deleteRecordedAudio() {
96 | state = .Initial
97 | }
98 |
99 | func saveRecordedAudioWithName(name: String?) {
100 | do {
101 | let voiceName = (name ?? NSLocalizedString("未命名", comment: "")) + " " + NSUUID().UUIDString
102 |
103 | let voice: Voice = try CoreDataStack.createNewObject()
104 | voice.date = NSDate()
105 | voice.duration = recordDuration
106 | voice.name = voiceName
107 |
108 | var storeURL = try FileHandler.getDirectoryURL()
109 | storeURL = storeURL.URLByAppendingPathComponent(voiceName)
110 | try NSFileManager.defaultManager().moveItemAtURL(tmpStoreURL, toURL: storeURL)
111 | CoreDataStack.save()
112 | } catch let error {
113 | debugPrint(error)
114 | delegate?.handleVoiceSaveError()
115 | }
116 |
117 | state = .Initial
118 | }
119 |
120 | // MARK: Convience
121 |
122 | private func prepareForRecording() {
123 | let session:AVAudioSession = AVAudioSession.sharedInstance()
124 | session.requestRecordPermission { granted in
125 | if granted {
126 | debugPrint("Recording permission has been granted")
127 |
128 | let recordSettings: [String : AnyObject] = [
129 | AVFormatIDKey : NSNumber(unsignedInt: kAudioFormatLinearPCM),
130 | AVSampleRateKey : 44100.0,
131 | AVNumberOfChannelsKey : 2,
132 | AVLinearPCMBitDepthKey : 16,
133 | AVLinearPCMIsBigEndianKey : false,
134 | AVLinearPCMIsFloatKey : false,
135 | ]
136 |
137 | do {
138 | self.audioRecorder = try AVAudioRecorder(URL: self.tmpStoreURL, settings: recordSettings)
139 | self.audioRecorder?.delegate = self
140 | } catch {
141 | self.delegate?.handleRecorderFetchError()
142 | }
143 | } else {
144 | debugPrint("Recording permission has not been granted")
145 |
146 | self.delegate?.hanldePermissionError()
147 | }
148 | }
149 | }
150 |
151 | private func handleInterruption(notification: NSNotification) {
152 | if let userInfo = notification.userInfo {
153 | let interruptionType = userInfo[AVAudioSessionInterruptionTypeKey] as! UInt
154 | if interruptionType == AVAudioSessionInterruptionType.Began.rawValue {
155 | recordTimer = nil
156 | if audioRecorder?.recording == true {
157 | audioRecorder?.stop()
158 | audioRecorder?.deleteRecording()
159 | state = .Initial
160 | }
161 | } else if interruptionType == AVAudioSessionInterruptionType.Ended.rawValue {
162 | delegate?.handleRecordingInterrupted()
163 | }
164 | }
165 | }
166 |
167 | //MARK: RecordingManagerDelegate
168 |
169 | func updateDuration() {
170 | if let duration = audioRecorder?.currentTime {
171 | delegate?.updateRecordedTimeInterval(duration)
172 | }
173 | }
174 |
175 | // MARK: AVAudioRecorderDelegate
176 |
177 | func audioRecorderEncodeErrorDidOccur(recorder: AVAudioRecorder, error: NSError?) {
178 | assertionFailure()
179 |
180 | delegate?.handleRecorderEncodingError()
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/VoiceMemo/Recording /RecordingViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/25/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class RecordingViewController: UIViewController, SegueHandlerType, TextFieldAlertDelegate, PlayManagerDelegate {
12 |
13 | // MARK: Properties
14 |
15 | @IBOutlet weak var playButton: UIButton!
16 | @IBOutlet weak var recordImageView: UIImageView!
17 | @IBOutlet weak var saveButton: UIButton!
18 | @IBOutlet weak var timeLabel: UILabel!
19 |
20 | lazy var recordingManager: RecordingManager = {
21 | let recordingManager = RecordingManager(delegate: self)
22 | return recordingManager
23 | }()
24 |
25 | lazy var playManager: PlayManager = {
26 | let playManager = PlayManager(delegate: self)
27 | return playManager
28 | }()
29 |
30 | var audioURL: NSURL?
31 |
32 | // MARK: View Life Cycle
33 |
34 | override func viewDidLoad() {
35 | super.viewDidLoad()
36 |
37 | state = .Initial
38 | }
39 |
40 | override func viewWillAppear(animated: Bool) {
41 | super.viewWillAppear(animated)
42 |
43 | playManager.delegate = self
44 | }
45 |
46 | // MARK: Segue
47 |
48 | enum SegueIdentifier: String {
49 | case ShowVoiceList
50 | }
51 |
52 | override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
53 | if identifier == SegueIdentifier.ShowVoiceList.rawValue {
54 | switch state {
55 | case .Initial:
56 | return true
57 | default:
58 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
59 | })
60 | let delete: AlertActionTuple = (title: NSLocalizedString("删除当前音频备忘录", comment: ""), style: .Destructive, { action in
61 | self.recordingManager.deleteRecordedAudio()
62 | self.state = .Initial
63 | self.performSegueWithIdentifier(.ShowVoiceList, sender: self)
64 | })
65 | let save: AlertActionTuple = (title: NSLocalizedString("保存当前音频备忘录", comment: ""), style: .Default, { action in
66 | self.save()
67 | self.state = .Initial
68 | self.performSegueWithIdentifier(.ShowVoiceList, sender: self)
69 | })
70 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("您已经录制了一个音频备忘录, 请选择如何处理当前备忘录", comment: ""), message: nil, actionTuples: [cancel, delete, save])
71 | return false
72 | }
73 | }
74 | return true
75 | }
76 |
77 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
78 | let segueIdentifier = segueIdentifierForSegue(segue)
79 |
80 | switch segueIdentifier {
81 | case .ShowVoiceList:
82 | if let destinationVC = segue.destinationViewController as? VoiceListViewController {
83 | destinationVC.playManager = playManager
84 | }
85 | }
86 | }
87 |
88 | // MARK: State Change
89 |
90 | enum RecordingState {
91 | case Initial
92 | case Recording
93 | case RecordingFinished
94 | case Playing
95 | case PlayPaused
96 | }
97 |
98 | var state: RecordingState = .Initial {
99 | didSet {
100 | switch state {
101 | case .Initial:
102 | playButton.enabled = false
103 | playButton.setImage(UIImage(named: "play_begin"), forState: .Normal)
104 | saveButton.enabled = false
105 | saveButton.alpha = 0.8
106 | timeLabel.text = NSLocalizedString("长按录音键开始录音", comment: "")
107 | audioURL = nil
108 | case .Recording:
109 | playButton.enabled = false
110 | playButton.setImage(UIImage(named: "play_begin"), forState: .Normal)
111 | saveButton.enabled = false
112 | saveButton.alpha = 0.8
113 | timeLabel.text = NSLocalizedString("放开录音键停止录音", comment: "")
114 | case .RecordingFinished:
115 | playButton.enabled = true
116 | playButton.setImage(UIImage(named: "play_begin"), forState: .Normal)
117 | saveButton.enabled = true
118 | saveButton.alpha = 1.0
119 | audioURL = recordingManager.tmpStoreURL
120 | timeLabel.text = NSLocalizedString("点击播放按钮播放, 或者点击保存按钮保存", comment: "")
121 | case .Playing:
122 | playButton.enabled = true
123 | playButton.setImage(UIImage(named: "play_pause"), forState: .Normal)
124 | saveButton.enabled = false
125 | case .PlayPaused:
126 | playButton.enabled = true
127 | playButton.setImage(UIImage(named: "play_begin"), forState: .Normal)
128 | saveButton.enabled = true
129 | saveButton.alpha = 1.0
130 | }
131 | }
132 | }
133 |
134 | @IBAction func playAudio(sender: UIButton) {
135 | if state == .Playing {
136 | playManager.togglePlayState()
137 | state = .PlayPaused
138 | } else if state == .PlayPaused {
139 | playManager.togglePlayState()
140 | state = .Playing
141 | } else {
142 | if let URL = audioURL {
143 | playManager.playItem(URL)
144 | state = .Playing
145 | } else {
146 | assertionFailure()
147 | }
148 | state = .Playing
149 | }
150 | }
151 |
152 | @IBAction func saveAudio(sender: UIButton) {
153 | save()
154 | }
155 |
156 | func save() {
157 | Alert.showInputAlertWithSourceViewController(self, title: NSLocalizedString("标题", comment: ""), message: NSLocalizedString("请输入当前备忘录标题", comment: ""), confirmTitle: "保存", delegate: self)
158 | }
159 |
160 | func inputFinishedWithContent(content: String?) {
161 | recordingManager.saveRecordedAudioWithName(content)
162 | state = .Initial
163 | }
164 |
165 | @IBAction func recordAudio(sender: UILongPressGestureRecognizer) {
166 | switch sender.state {
167 | case .Began:
168 | do {
169 | try recordingManager.startNewRecording()
170 | state = .Recording
171 | } catch RecordingError.AudioExist {
172 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
173 | self.state = .RecordingFinished
174 | })
175 | let delete: AlertActionTuple = (title: NSLocalizedString("删除旧的音频备忘录", comment: ""), style: .Destructive, { action in
176 | self.recordingManager.deleteRecordedAudio()
177 | self.state = .Initial
178 | })
179 | let save: AlertActionTuple = (title: NSLocalizedString("保存音频备忘录", comment: ""), style: .Default, { action in
180 | self.save()
181 | self.state = .Initial
182 | })
183 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("您已经录制了一个音频备忘录, 请保存旧的备忘录或者删除当前音频备忘录再进行新的录制", comment: ""), message: nil, actionTuples: [cancel, delete, save])
184 | } catch let error {
185 | debugPrint("\(error)")
186 | handleRecordingInterrupted()
187 | }
188 | case .Ended:
189 | state = .RecordingFinished
190 | recordingManager.finishRecording()
191 | default:()
192 | }
193 | }
194 |
195 | }
196 |
--------------------------------------------------------------------------------
/VoiceMemo/AudioList/VoiceListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VoiceListViewController.swift
3 | // VoiceMemo
4 | //
5 | // Created by Shannon Wu on 12/25/15.
6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreData
11 |
12 | // MARK: - VoiceListCell
13 |
14 | enum VoiceListCellState {
15 | case NotCurrent
16 | case Current
17 | }
18 |
19 | class VoiceListCell: UITableViewCell {
20 | @IBOutlet weak var progressView: CircularProgressView!
21 | @IBOutlet weak var titleLabel: UILabel!
22 | @IBOutlet weak var dateLabel: UILabel!
23 | @IBOutlet weak var durationLabel: UILabel!
24 |
25 | var state = VoiceListCellState.NotCurrent {
26 | didSet {
27 | switch state {
28 | case .NotCurrent:
29 | progressView.progress = 0
30 | progressView.iconStyle = .Play
31 | case .Current:()
32 | }
33 | }
34 | }
35 |
36 | func configureCellWithVoice(voice: Voice) {
37 | titleLabel.text = voice.name.segmentsWithSeparator().first!
38 | dateLabel.text = voice.date.stringValue
39 | durationLabel.text = voice.duration.format(2) + " s"
40 | }
41 |
42 | override func prepareForReuse() {
43 | state = .NotCurrent
44 | }
45 | }
46 |
47 | // MARK: - VoiceListViewController
48 |
49 | class VoiceListViewController: UITableViewController {
50 | // MARK: Properties
51 | var fetchedResultsController : NSFetchedResultsController!
52 | var playManager: PlayManager? {
53 | didSet {
54 | playManager?.delegate = self
55 | }
56 | }
57 | var currentVoice: Voice?
58 | var currentCell: VoiceListCell? {
59 | if let indexPath = playingIndexpath,
60 | cell = tableView.cellForRowAtIndexPath(indexPath) as? VoiceListCell {
61 | return cell
62 | }
63 | return nil
64 | }
65 | var shouldRestorePlayingIndexpath = false
66 | var previousPlayingIndexpath: NSIndexPath?
67 | var previousVell: VoiceListCell? {
68 | if let indexPath = previousPlayingIndexpath,
69 | cell = tableView.cellForRowAtIndexPath(indexPath) as? VoiceListCell {
70 | return cell
71 | }
72 | return nil
73 | }
74 |
75 | var playingIndexpath: NSIndexPath? {
76 | didSet {
77 | if playingIndexpath == oldValue {
78 | if let cell = tableView.cellForRowAtIndexPath(playingIndexpath!) as? VoiceListCell {
79 | if cell.progressView.iconStyle == .Play {
80 | cell.progressView.iconStyle = .Pause
81 | } else {
82 | cell.progressView.iconStyle = .Play
83 | }
84 |
85 | playManager?.togglePlayState()
86 | return
87 | }
88 | }
89 |
90 | if previousPlayingIndexpath != nil && playingIndexpath != nil {
91 | previousVell?.state = .NotCurrent
92 | }
93 | if let indexPath = playingIndexpath,
94 | cell = tableView.cellForRowAtIndexPath(indexPath) as? VoiceListCell {
95 | let voice = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Voice
96 | cell.state = .Current
97 | if let URL = voice.fetchPathURL() {
98 | playManager?.playItem(URL)
99 | } else {
100 | assertionFailure()
101 | }
102 | }
103 | }
104 | }
105 |
106 | // MARK: View Life Cycle
107 | override func viewDidLoad() {
108 | super.viewDidLoad()
109 |
110 | let fetchRequest = NSFetchRequest(entityName: "Voice")
111 | fetchRequest.fetchBatchSize = 20
112 | let keySort = NSSortDescriptor(key: "date", ascending: false)
113 | fetchRequest.sortDescriptors = [keySort]
114 | fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
115 | managedObjectContext: CoreDataStack.context,
116 | sectionNameKeyPath: nil,
117 | cacheName: nil)
118 | fetchedResultsController.delegate = self
119 | do {
120 | try fetchedResultsController.performFetch()
121 | } catch {
122 | debugPrint("无法获取数据库数据: \(error)")
123 | }
124 |
125 | tableView.estimatedRowHeight = 100.0
126 | tableView.tableFooterView = UIView()
127 | }
128 |
129 | // MARK: Convience
130 |
131 | func deleteVoice(voice: Voice) {
132 | playManager?.stop()
133 |
134 | do {
135 | let removeFileURL = try FileHandler.getDirectoryURL().URLByAppendingPathComponent(voice.name)
136 | try NSFileManager.defaultManager().removeItemAtURL(removeFileURL)
137 | CoreDataStack.context.deleteObject(voice)
138 | CoreDataStack.save()
139 | } catch let error {
140 | assertionFailure("\(error)")
141 | }
142 | }
143 |
144 | func restoreCell(cell: VoiceListCell) {
145 | cell.progressView.progress = 1.0
146 | cell.progressView.iconStyle = .Play
147 | }
148 |
149 | }
150 |
151 | // MARK: UITableViewDataSource
152 |
153 | extension VoiceListViewController {
154 |
155 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
156 | return fetchedResultsController.sections!.count
157 | }
158 |
159 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
160 | let sectionInfo = fetchedResultsController.sections![section]
161 | return sectionInfo.numberOfObjects
162 | }
163 |
164 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
165 | return tableView.dequeueReusableCellWithIdentifier("Cell") as! VoiceListCell
166 | }
167 |
168 | override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
169 | return true
170 | }
171 |
172 | override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
173 | switch editingStyle {
174 | case .Delete:
175 | let voice = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Voice
176 | deleteVoice(voice)
177 | default:
178 | break
179 | }
180 | }
181 |
182 | }
183 |
184 | // MARK: UITableViewDelegate
185 |
186 | extension VoiceListViewController {
187 | override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
188 | let voice = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Voice
189 | (cell as! VoiceListCell).configureCellWithVoice(voice)
190 |
191 | if indexPath == previousPlayingIndexpath && shouldRestorePlayingIndexpath {
192 | restoreCell(cell as! VoiceListCell)
193 | }
194 | }
195 |
196 | override func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
197 | if indexPath == previousPlayingIndexpath {
198 | shouldRestorePlayingIndexpath = true
199 | }
200 | }
201 |
202 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
203 | playingIndexpath = indexPath
204 | previousPlayingIndexpath = nil
205 | tableView.deselectRowAtIndexPath(indexPath, animated: true)
206 | }
207 | }
208 |
209 |
210 | // MARK: NSFetchedResultsControllerDelegate
211 |
212 | extension VoiceListViewController: NSFetchedResultsControllerDelegate {
213 |
214 | func controllerWillChangeContent(controller: NSFetchedResultsController) {
215 | tableView.beginUpdates()
216 | }
217 |
218 | func controller(controller: NSFetchedResultsController,
219 | didChangeObject anObject: AnyObject,
220 | atIndexPath indexPath: NSIndexPath?,
221 | forChangeType type: NSFetchedResultsChangeType,
222 | newIndexPath: NSIndexPath?) {
223 | switch type {
224 | case .Insert,
225 | .Update,
226 | .Move:
227 | assertionFailure("没有实现这些操作")
228 | case .Delete:
229 | tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
230 | }
231 |
232 | }
233 |
234 | func controllerDidChangeContent(controller: NSFetchedResultsController) {
235 | tableView.endUpdates()
236 | }
237 |
238 | }
239 |
240 | extension VoiceListViewController: PlayManagerDelegate {
241 | func updatePlayStatusWithCurrentTime(currentTime: NSTimeInterval, totalTime: NSTimeInterval) {
242 | let progress = currentTime / totalTime
243 | currentCell?.progressView.progress = CGFloat(progress)
244 | }
245 |
246 | func playerDidChangeToState(state: PlaybackState) {
247 | switch state {
248 | case .Play:
249 | currentCell?.progressView.iconStyle = .Pause
250 | case .Initial,
251 | .Pause:
252 | currentCell?.progressView.iconStyle = .Play
253 | case .Finished:
254 | if currentCell == nil {
255 | shouldRestorePlayingIndexpath = true
256 | } else {
257 | currentCell?.progressView.progress = 1.0
258 | currentCell?.progressView.iconStyle = .Play
259 | shouldRestorePlayingIndexpath = false
260 | }
261 | previousPlayingIndexpath = playingIndexpath
262 | playingIndexpath = nil
263 | }
264 | }
265 |
266 | func handlePlaybackError() {
267 | let cancel: AlertActionTuple = (title: NSLocalizedString("返回", comment: ""), style: .Cancel, { action in
268 |
269 | })
270 | Alert.showAlertWithSourceViewController(self, title: NSLocalizedString("播放错误, 请重试", comment: ""), message: nil, actionTuples: [cancel])
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/VoiceMemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
35 |
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 |
126 |
132 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
--------------------------------------------------------------------------------
/VoiceMemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 610CA74C1C2E2DFE00436646 /* RecordingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610CA74B1C2E2DFE00436646 /* RecordingManager.swift */; };
11 | 610CA74E1C2E353C00436646 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610CA74D1C2E353C00436646 /* Alert.swift */; };
12 | 610CA7501C2E406400436646 /* PlayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610CA74F1C2E406400436646 /* PlayManager.swift */; };
13 | 610CA7521C2E411700436646 /* AudioSessionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610CA7511C2E411700436646 /* AudioSessionHelper.swift */; };
14 | 610CA7541C2E58B400436646 /* FileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610CA7531C2E58B400436646 /* FileHandler.swift */; };
15 | 610CA7561C2E5A6700436646 /* DateHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610CA7551C2E5A6700436646 /* DateHandler.swift */; };
16 | 612545221C2D8AB5006281D2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612545211C2D8AB5006281D2 /* AppDelegate.swift */; };
17 | 612545241C2D8AB5006281D2 /* RecordingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612545231C2D8AB5006281D2 /* RecordingViewController.swift */; };
18 | 612545271C2D8AB5006281D2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 612545251C2D8AB5006281D2 /* Main.storyboard */; };
19 | 6125452A1C2D8AB5006281D2 /* VoiceMemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 612545281C2D8AB5006281D2 /* VoiceMemo.xcdatamodeld */; };
20 | 6125452C1C2D8AB5006281D2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6125452B1C2D8AB5006281D2 /* Assets.xcassets */; };
21 | 6125452F1C2D8AB5006281D2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6125452D1C2D8AB5006281D2 /* LaunchScreen.storyboard */; };
22 | 612545571C2D8E66006281D2 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612545561C2D8E66006281D2 /* CoreDataStack.swift */; };
23 | 612545611C2D91E2006281D2 /* VoiceListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612545601C2D91E2006281D2 /* VoiceListViewController.swift */; };
24 | 612545A21C2DA4E0006281D2 /* Voice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612545A11C2DA4E0006281D2 /* Voice.swift */; };
25 | 612545A71C2DB0DE006281D2 /* SegueHandlerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612545A61C2DB0DE006281D2 /* SegueHandlerType.swift */; };
26 | 613B807D1C2E66CB00DFFABB /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613B807C1C2E66CB00DFFABB /* Utils.swift */; };
27 | 613B807F1C2E776900DFFABB /* RecordingViewController+Recording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613B807E1C2E776900DFFABB /* RecordingViewController+Recording.swift */; };
28 | 618432B01C2ED14900759CBC /* RecordingViewController+Playing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618432AF1C2ED14900759CBC /* RecordingViewController+Playing.swift */; };
29 | 618432B31C2ED8B400759CBC /* CircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618432B21C2ED8B400759CBC /* CircularProgressView.swift */; };
30 | /* End PBXBuildFile section */
31 |
32 | /* Begin PBXContainerItemProxy section */
33 | 612545361C2D8AB5006281D2 /* PBXContainerItemProxy */ = {
34 | isa = PBXContainerItemProxy;
35 | containerPortal = 612545161C2D8AB5006281D2 /* Project object */;
36 | proxyType = 1;
37 | remoteGlobalIDString = 6125451D1C2D8AB5006281D2;
38 | remoteInfo = VoiceMemo;
39 | };
40 | 612545411C2D8AB5006281D2 /* PBXContainerItemProxy */ = {
41 | isa = PBXContainerItemProxy;
42 | containerPortal = 612545161C2D8AB5006281D2 /* Project object */;
43 | proxyType = 1;
44 | remoteGlobalIDString = 6125451D1C2D8AB5006281D2;
45 | remoteInfo = VoiceMemo;
46 | };
47 | /* End PBXContainerItemProxy section */
48 |
49 | /* Begin PBXFileReference section */
50 | 610CA74B1C2E2DFE00436646 /* RecordingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordingManager.swift; sourceTree = ""; };
51 | 610CA74D1C2E353C00436646 /* Alert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; };
52 | 610CA74F1C2E406400436646 /* PlayManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayManager.swift; sourceTree = ""; };
53 | 610CA7511C2E411700436646 /* AudioSessionHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSessionHelper.swift; sourceTree = ""; };
54 | 610CA7531C2E58B400436646 /* FileHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHandler.swift; sourceTree = ""; };
55 | 610CA7551C2E5A6700436646 /* DateHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateHandler.swift; sourceTree = ""; };
56 | 6125451E1C2D8AB5006281D2 /* VoiceMemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VoiceMemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
57 | 612545211C2D8AB5006281D2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
58 | 612545231C2D8AB5006281D2 /* RecordingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingViewController.swift; sourceTree = ""; };
59 | 612545261C2D8AB5006281D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
60 | 612545291C2D8AB5006281D2 /* VoiceMemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = VoiceMemo.xcdatamodel; sourceTree = ""; };
61 | 6125452B1C2D8AB5006281D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
62 | 6125452E1C2D8AB5006281D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
63 | 612545301C2D8AB5006281D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
64 | 612545351C2D8AB5006281D2 /* VoiceMemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VoiceMemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
65 | 6125453B1C2D8AB5006281D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
66 | 612545401C2D8AB5006281D2 /* VoiceMemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VoiceMemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
67 | 612545461C2D8AB5006281D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
68 | 612545561C2D8E66006281D2 /* CoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = ""; };
69 | 612545601C2D91E2006281D2 /* VoiceListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoiceListViewController.swift; sourceTree = ""; };
70 | 612545A11C2DA4E0006281D2 /* Voice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Voice.swift; sourceTree = ""; };
71 | 612545A61C2DB0DE006281D2 /* SegueHandlerType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegueHandlerType.swift; sourceTree = ""; };
72 | 613B807C1C2E66CB00DFFABB /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; };
73 | 613B807E1C2E776900DFFABB /* RecordingViewController+Recording.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RecordingViewController+Recording.swift"; sourceTree = ""; };
74 | 618432AF1C2ED14900759CBC /* RecordingViewController+Playing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RecordingViewController+Playing.swift"; sourceTree = ""; };
75 | 618432B21C2ED8B400759CBC /* CircularProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = ""; };
76 | /* End PBXFileReference section */
77 |
78 | /* Begin PBXFrameworksBuildPhase section */
79 | 6125451B1C2D8AB5006281D2 /* Frameworks */ = {
80 | isa = PBXFrameworksBuildPhase;
81 | buildActionMask = 2147483647;
82 | files = (
83 | );
84 | runOnlyForDeploymentPostprocessing = 0;
85 | };
86 | 612545321C2D8AB5006281D2 /* Frameworks */ = {
87 | isa = PBXFrameworksBuildPhase;
88 | buildActionMask = 2147483647;
89 | files = (
90 | );
91 | runOnlyForDeploymentPostprocessing = 0;
92 | };
93 | 6125453D1C2D8AB5006281D2 /* Frameworks */ = {
94 | isa = PBXFrameworksBuildPhase;
95 | buildActionMask = 2147483647;
96 | files = (
97 | );
98 | runOnlyForDeploymentPostprocessing = 0;
99 | };
100 | /* End PBXFrameworksBuildPhase section */
101 |
102 | /* Begin PBXGroup section */
103 | 612545151C2D8AB5006281D2 = {
104 | isa = PBXGroup;
105 | children = (
106 | 6125451F1C2D8AB5006281D2 /* Products */,
107 | 612545201C2D8AB5006281D2 /* VoiceMemo */,
108 | 612545381C2D8AB5006281D2 /* VoiceMemoTests */,
109 | 612545431C2D8AB5006281D2 /* VoiceMemoUITests */,
110 | );
111 | sourceTree = "";
112 | };
113 | 6125451F1C2D8AB5006281D2 /* Products */ = {
114 | isa = PBXGroup;
115 | children = (
116 | 6125451E1C2D8AB5006281D2 /* VoiceMemo.app */,
117 | 612545351C2D8AB5006281D2 /* VoiceMemoTests.xctest */,
118 | 612545401C2D8AB5006281D2 /* VoiceMemoUITests.xctest */,
119 | );
120 | name = Products;
121 | sourceTree = "";
122 | };
123 | 612545201C2D8AB5006281D2 /* VoiceMemo */ = {
124 | isa = PBXGroup;
125 | children = (
126 | 618432B11C2ED89900759CBC /* AudioList */,
127 | 612545551C2D8B7B006281D2 /* Data */,
128 | 613B80811C2E7BD800DFFABB /* Player */,
129 | 613B80801C2E779300DFFABB /* Recording */,
130 | 612545541C2D8B42006281D2 /* Supporting Files */,
131 | 612545A51C2DAB03006281D2 /* Utils */,
132 | 612545251C2D8AB5006281D2 /* Main.storyboard */,
133 | );
134 | path = VoiceMemo;
135 | sourceTree = "";
136 | };
137 | 612545381C2D8AB5006281D2 /* VoiceMemoTests */ = {
138 | isa = PBXGroup;
139 | children = (
140 | 612545531C2D8B38006281D2 /* Supporting Files */,
141 | );
142 | path = VoiceMemoTests;
143 | sourceTree = "";
144 | };
145 | 612545431C2D8AB5006281D2 /* VoiceMemoUITests */ = {
146 | isa = PBXGroup;
147 | children = (
148 | 612545521C2D8B2C006281D2 /* Supporting Files */,
149 | );
150 | path = VoiceMemoUITests;
151 | sourceTree = "";
152 | };
153 | 612545521C2D8B2C006281D2 /* Supporting Files */ = {
154 | isa = PBXGroup;
155 | children = (
156 | 612545461C2D8AB5006281D2 /* Info.plist */,
157 | );
158 | path = "Supporting Files";
159 | sourceTree = "";
160 | };
161 | 612545531C2D8B38006281D2 /* Supporting Files */ = {
162 | isa = PBXGroup;
163 | children = (
164 | 6125453B1C2D8AB5006281D2 /* Info.plist */,
165 | );
166 | path = "Supporting Files";
167 | sourceTree = "";
168 | };
169 | 612545541C2D8B42006281D2 /* Supporting Files */ = {
170 | isa = PBXGroup;
171 | children = (
172 | 612545211C2D8AB5006281D2 /* AppDelegate.swift */,
173 | 6125452B1C2D8AB5006281D2 /* Assets.xcassets */,
174 | 612545301C2D8AB5006281D2 /* Info.plist */,
175 | 6125452D1C2D8AB5006281D2 /* LaunchScreen.storyboard */,
176 | );
177 | path = "Supporting Files";
178 | sourceTree = "";
179 | };
180 | 612545551C2D8B7B006281D2 /* Data */ = {
181 | isa = PBXGroup;
182 | children = (
183 | 612545561C2D8E66006281D2 /* CoreDataStack.swift */,
184 | 610CA7551C2E5A6700436646 /* DateHandler.swift */,
185 | 610CA7531C2E58B400436646 /* FileHandler.swift */,
186 | 612545A11C2DA4E0006281D2 /* Voice.swift */,
187 | 612545281C2D8AB5006281D2 /* VoiceMemo.xcdatamodeld */,
188 | );
189 | path = Data;
190 | sourceTree = "";
191 | };
192 | 612545A51C2DAB03006281D2 /* Utils */ = {
193 | isa = PBXGroup;
194 | children = (
195 | 610CA74D1C2E353C00436646 /* Alert.swift */,
196 | 610CA7511C2E411700436646 /* AudioSessionHelper.swift */,
197 | 612545A61C2DB0DE006281D2 /* SegueHandlerType.swift */,
198 | 613B807C1C2E66CB00DFFABB /* Utils.swift */,
199 | );
200 | path = Utils;
201 | sourceTree = "";
202 | };
203 | 613B80801C2E779300DFFABB /* Recording */ = {
204 | isa = PBXGroup;
205 | children = (
206 | 610CA74B1C2E2DFE00436646 /* RecordingManager.swift */,
207 | 618432AF1C2ED14900759CBC /* RecordingViewController+Playing.swift */,
208 | 613B807E1C2E776900DFFABB /* RecordingViewController+Recording.swift */,
209 | 612545231C2D8AB5006281D2 /* RecordingViewController.swift */,
210 | );
211 | path = "Recording ";
212 | sourceTree = "";
213 | };
214 | 613B80811C2E7BD800DFFABB /* Player */ = {
215 | isa = PBXGroup;
216 | children = (
217 | 610CA74F1C2E406400436646 /* PlayManager.swift */,
218 | );
219 | path = Player;
220 | sourceTree = "";
221 | };
222 | 618432B11C2ED89900759CBC /* AudioList */ = {
223 | isa = PBXGroup;
224 | children = (
225 | 618432B21C2ED8B400759CBC /* CircularProgressView.swift */,
226 | 612545601C2D91E2006281D2 /* VoiceListViewController.swift */,
227 | );
228 | path = AudioList;
229 | sourceTree = "";
230 | };
231 | /* End PBXGroup section */
232 |
233 | /* Begin PBXNativeTarget section */
234 | 6125451D1C2D8AB5006281D2 /* VoiceMemo */ = {
235 | isa = PBXNativeTarget;
236 | buildConfigurationList = 612545491C2D8AB5006281D2 /* Build configuration list for PBXNativeTarget "VoiceMemo" */;
237 | buildPhases = (
238 | 6125451A1C2D8AB5006281D2 /* Sources */,
239 | 6125451B1C2D8AB5006281D2 /* Frameworks */,
240 | 6125451C1C2D8AB5006281D2 /* Resources */,
241 | );
242 | buildRules = (
243 | );
244 | dependencies = (
245 | );
246 | name = VoiceMemo;
247 | productName = VoiceMemo;
248 | productReference = 6125451E1C2D8AB5006281D2 /* VoiceMemo.app */;
249 | productType = "com.apple.product-type.application";
250 | };
251 | 612545341C2D8AB5006281D2 /* VoiceMemoTests */ = {
252 | isa = PBXNativeTarget;
253 | buildConfigurationList = 6125454C1C2D8AB5006281D2 /* Build configuration list for PBXNativeTarget "VoiceMemoTests" */;
254 | buildPhases = (
255 | 612545311C2D8AB5006281D2 /* Sources */,
256 | 612545321C2D8AB5006281D2 /* Frameworks */,
257 | 612545331C2D8AB5006281D2 /* Resources */,
258 | );
259 | buildRules = (
260 | );
261 | dependencies = (
262 | 612545371C2D8AB5006281D2 /* PBXTargetDependency */,
263 | );
264 | name = VoiceMemoTests;
265 | productName = VoiceMemoTests;
266 | productReference = 612545351C2D8AB5006281D2 /* VoiceMemoTests.xctest */;
267 | productType = "com.apple.product-type.bundle.unit-test";
268 | };
269 | 6125453F1C2D8AB5006281D2 /* VoiceMemoUITests */ = {
270 | isa = PBXNativeTarget;
271 | buildConfigurationList = 6125454F1C2D8AB5006281D2 /* Build configuration list for PBXNativeTarget "VoiceMemoUITests" */;
272 | buildPhases = (
273 | 6125453C1C2D8AB5006281D2 /* Sources */,
274 | 6125453D1C2D8AB5006281D2 /* Frameworks */,
275 | 6125453E1C2D8AB5006281D2 /* Resources */,
276 | );
277 | buildRules = (
278 | );
279 | dependencies = (
280 | 612545421C2D8AB5006281D2 /* PBXTargetDependency */,
281 | );
282 | name = VoiceMemoUITests;
283 | productName = VoiceMemoUITests;
284 | productReference = 612545401C2D8AB5006281D2 /* VoiceMemoUITests.xctest */;
285 | productType = "com.apple.product-type.bundle.ui-testing";
286 | };
287 | /* End PBXNativeTarget section */
288 |
289 | /* Begin PBXProject section */
290 | 612545161C2D8AB5006281D2 /* Project object */ = {
291 | isa = PBXProject;
292 | attributes = {
293 | LastSwiftUpdateCheck = 0720;
294 | LastUpgradeCheck = 0720;
295 | ORGANIZATIONNAME = "Shannon's Dreamland";
296 | TargetAttributes = {
297 | 6125451D1C2D8AB5006281D2 = {
298 | CreatedOnToolsVersion = 7.2;
299 | };
300 | 612545341C2D8AB5006281D2 = {
301 | CreatedOnToolsVersion = 7.2;
302 | TestTargetID = 6125451D1C2D8AB5006281D2;
303 | };
304 | 6125453F1C2D8AB5006281D2 = {
305 | CreatedOnToolsVersion = 7.2;
306 | TestTargetID = 6125451D1C2D8AB5006281D2;
307 | };
308 | };
309 | };
310 | buildConfigurationList = 612545191C2D8AB5006281D2 /* Build configuration list for PBXProject "VoiceMemo" */;
311 | compatibilityVersion = "Xcode 3.2";
312 | developmentRegion = English;
313 | hasScannedForEncodings = 0;
314 | knownRegions = (
315 | en,
316 | Base,
317 | );
318 | mainGroup = 612545151C2D8AB5006281D2;
319 | productRefGroup = 6125451F1C2D8AB5006281D2 /* Products */;
320 | projectDirPath = "";
321 | projectRoot = "";
322 | targets = (
323 | 6125451D1C2D8AB5006281D2 /* VoiceMemo */,
324 | 612545341C2D8AB5006281D2 /* VoiceMemoTests */,
325 | 6125453F1C2D8AB5006281D2 /* VoiceMemoUITests */,
326 | );
327 | };
328 | /* End PBXProject section */
329 |
330 | /* Begin PBXResourcesBuildPhase section */
331 | 6125451C1C2D8AB5006281D2 /* Resources */ = {
332 | isa = PBXResourcesBuildPhase;
333 | buildActionMask = 2147483647;
334 | files = (
335 | 6125452F1C2D8AB5006281D2 /* LaunchScreen.storyboard in Resources */,
336 | 6125452C1C2D8AB5006281D2 /* Assets.xcassets in Resources */,
337 | 612545271C2D8AB5006281D2 /* Main.storyboard in Resources */,
338 | );
339 | runOnlyForDeploymentPostprocessing = 0;
340 | };
341 | 612545331C2D8AB5006281D2 /* Resources */ = {
342 | isa = PBXResourcesBuildPhase;
343 | buildActionMask = 2147483647;
344 | files = (
345 | );
346 | runOnlyForDeploymentPostprocessing = 0;
347 | };
348 | 6125453E1C2D8AB5006281D2 /* Resources */ = {
349 | isa = PBXResourcesBuildPhase;
350 | buildActionMask = 2147483647;
351 | files = (
352 | );
353 | runOnlyForDeploymentPostprocessing = 0;
354 | };
355 | /* End PBXResourcesBuildPhase section */
356 |
357 | /* Begin PBXSourcesBuildPhase section */
358 | 6125451A1C2D8AB5006281D2 /* Sources */ = {
359 | isa = PBXSourcesBuildPhase;
360 | buildActionMask = 2147483647;
361 | files = (
362 | 610CA7561C2E5A6700436646 /* DateHandler.swift in Sources */,
363 | 6125452A1C2D8AB5006281D2 /* VoiceMemo.xcdatamodeld in Sources */,
364 | 613B807F1C2E776900DFFABB /* RecordingViewController+Recording.swift in Sources */,
365 | 610CA74C1C2E2DFE00436646 /* RecordingManager.swift in Sources */,
366 | 613B807D1C2E66CB00DFFABB /* Utils.swift in Sources */,
367 | 612545241C2D8AB5006281D2 /* RecordingViewController.swift in Sources */,
368 | 612545571C2D8E66006281D2 /* CoreDataStack.swift in Sources */,
369 | 618432B31C2ED8B400759CBC /* CircularProgressView.swift in Sources */,
370 | 612545221C2D8AB5006281D2 /* AppDelegate.swift in Sources */,
371 | 610CA7541C2E58B400436646 /* FileHandler.swift in Sources */,
372 | 612545611C2D91E2006281D2 /* VoiceListViewController.swift in Sources */,
373 | 612545A21C2DA4E0006281D2 /* Voice.swift in Sources */,
374 | 618432B01C2ED14900759CBC /* RecordingViewController+Playing.swift in Sources */,
375 | 610CA7501C2E406400436646 /* PlayManager.swift in Sources */,
376 | 612545A71C2DB0DE006281D2 /* SegueHandlerType.swift in Sources */,
377 | 610CA7521C2E411700436646 /* AudioSessionHelper.swift in Sources */,
378 | 610CA74E1C2E353C00436646 /* Alert.swift in Sources */,
379 | );
380 | runOnlyForDeploymentPostprocessing = 0;
381 | };
382 | 612545311C2D8AB5006281D2 /* Sources */ = {
383 | isa = PBXSourcesBuildPhase;
384 | buildActionMask = 2147483647;
385 | files = (
386 | );
387 | runOnlyForDeploymentPostprocessing = 0;
388 | };
389 | 6125453C1C2D8AB5006281D2 /* Sources */ = {
390 | isa = PBXSourcesBuildPhase;
391 | buildActionMask = 2147483647;
392 | files = (
393 | );
394 | runOnlyForDeploymentPostprocessing = 0;
395 | };
396 | /* End PBXSourcesBuildPhase section */
397 |
398 | /* Begin PBXTargetDependency section */
399 | 612545371C2D8AB5006281D2 /* PBXTargetDependency */ = {
400 | isa = PBXTargetDependency;
401 | target = 6125451D1C2D8AB5006281D2 /* VoiceMemo */;
402 | targetProxy = 612545361C2D8AB5006281D2 /* PBXContainerItemProxy */;
403 | };
404 | 612545421C2D8AB5006281D2 /* PBXTargetDependency */ = {
405 | isa = PBXTargetDependency;
406 | target = 6125451D1C2D8AB5006281D2 /* VoiceMemo */;
407 | targetProxy = 612545411C2D8AB5006281D2 /* PBXContainerItemProxy */;
408 | };
409 | /* End PBXTargetDependency section */
410 |
411 | /* Begin PBXVariantGroup section */
412 | 612545251C2D8AB5006281D2 /* Main.storyboard */ = {
413 | isa = PBXVariantGroup;
414 | children = (
415 | 612545261C2D8AB5006281D2 /* Base */,
416 | );
417 | name = Main.storyboard;
418 | path = .;
419 | sourceTree = "";
420 | };
421 | 6125452D1C2D8AB5006281D2 /* LaunchScreen.storyboard */ = {
422 | isa = PBXVariantGroup;
423 | children = (
424 | 6125452E1C2D8AB5006281D2 /* Base */,
425 | );
426 | name = LaunchScreen.storyboard;
427 | path = .;
428 | sourceTree = "";
429 | };
430 | /* End PBXVariantGroup section */
431 |
432 | /* Begin XCBuildConfiguration section */
433 | 612545471C2D8AB5006281D2 /* Debug */ = {
434 | isa = XCBuildConfiguration;
435 | buildSettings = {
436 | ALWAYS_SEARCH_USER_PATHS = NO;
437 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
438 | CLANG_CXX_LIBRARY = "libc++";
439 | CLANG_ENABLE_MODULES = YES;
440 | CLANG_ENABLE_OBJC_ARC = YES;
441 | CLANG_WARN_BOOL_CONVERSION = YES;
442 | CLANG_WARN_CONSTANT_CONVERSION = YES;
443 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
444 | CLANG_WARN_EMPTY_BODY = YES;
445 | CLANG_WARN_ENUM_CONVERSION = YES;
446 | CLANG_WARN_INT_CONVERSION = YES;
447 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
448 | CLANG_WARN_UNREACHABLE_CODE = YES;
449 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
450 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
451 | COPY_PHASE_STRIP = NO;
452 | DEBUG_INFORMATION_FORMAT = dwarf;
453 | ENABLE_STRICT_OBJC_MSGSEND = YES;
454 | ENABLE_TESTABILITY = YES;
455 | GCC_C_LANGUAGE_STANDARD = gnu99;
456 | GCC_DYNAMIC_NO_PIC = NO;
457 | GCC_NO_COMMON_BLOCKS = YES;
458 | GCC_OPTIMIZATION_LEVEL = 0;
459 | GCC_PREPROCESSOR_DEFINITIONS = (
460 | "DEBUG=1",
461 | "$(inherited)",
462 | );
463 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
464 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
465 | GCC_WARN_UNDECLARED_SELECTOR = YES;
466 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
467 | GCC_WARN_UNUSED_FUNCTION = YES;
468 | GCC_WARN_UNUSED_VARIABLE = YES;
469 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
470 | MTL_ENABLE_DEBUG_INFO = YES;
471 | ONLY_ACTIVE_ARCH = YES;
472 | SDKROOT = iphoneos;
473 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
474 | };
475 | name = Debug;
476 | };
477 | 612545481C2D8AB5006281D2 /* Release */ = {
478 | isa = XCBuildConfiguration;
479 | buildSettings = {
480 | ALWAYS_SEARCH_USER_PATHS = NO;
481 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
482 | CLANG_CXX_LIBRARY = "libc++";
483 | CLANG_ENABLE_MODULES = YES;
484 | CLANG_ENABLE_OBJC_ARC = YES;
485 | CLANG_WARN_BOOL_CONVERSION = YES;
486 | CLANG_WARN_CONSTANT_CONVERSION = YES;
487 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
488 | CLANG_WARN_EMPTY_BODY = YES;
489 | CLANG_WARN_ENUM_CONVERSION = YES;
490 | CLANG_WARN_INT_CONVERSION = YES;
491 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
492 | CLANG_WARN_UNREACHABLE_CODE = YES;
493 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
494 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
495 | COPY_PHASE_STRIP = NO;
496 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
497 | ENABLE_NS_ASSERTIONS = NO;
498 | ENABLE_STRICT_OBJC_MSGSEND = YES;
499 | GCC_C_LANGUAGE_STANDARD = gnu99;
500 | GCC_NO_COMMON_BLOCKS = YES;
501 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
502 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
503 | GCC_WARN_UNDECLARED_SELECTOR = YES;
504 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
505 | GCC_WARN_UNUSED_FUNCTION = YES;
506 | GCC_WARN_UNUSED_VARIABLE = YES;
507 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
508 | MTL_ENABLE_DEBUG_INFO = NO;
509 | SDKROOT = iphoneos;
510 | VALIDATE_PRODUCT = YES;
511 | };
512 | name = Release;
513 | };
514 | 6125454A1C2D8AB5006281D2 /* Debug */ = {
515 | isa = XCBuildConfiguration;
516 | buildSettings = {
517 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
518 | INFOPLIST_FILE = "VoiceMemo/Supporting Files/Info.plist";
519 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
520 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
521 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.VoiceMemo;
522 | PRODUCT_NAME = "$(TARGET_NAME)";
523 | };
524 | name = Debug;
525 | };
526 | 6125454B1C2D8AB5006281D2 /* Release */ = {
527 | isa = XCBuildConfiguration;
528 | buildSettings = {
529 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
530 | INFOPLIST_FILE = "VoiceMemo/Supporting Files/Info.plist";
531 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
532 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
533 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.VoiceMemo;
534 | PRODUCT_NAME = "$(TARGET_NAME)";
535 | };
536 | name = Release;
537 | };
538 | 6125454D1C2D8AB5006281D2 /* Debug */ = {
539 | isa = XCBuildConfiguration;
540 | buildSettings = {
541 | BUNDLE_LOADER = "$(TEST_HOST)";
542 | INFOPLIST_FILE = "VoiceMemoTests/Supporting Files/Info.plist";
543 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
544 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.VoiceMemoTests;
545 | PRODUCT_NAME = "$(TARGET_NAME)";
546 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VoiceMemo.app/VoiceMemo";
547 | };
548 | name = Debug;
549 | };
550 | 6125454E1C2D8AB5006281D2 /* Release */ = {
551 | isa = XCBuildConfiguration;
552 | buildSettings = {
553 | BUNDLE_LOADER = "$(TEST_HOST)";
554 | INFOPLIST_FILE = "VoiceMemoTests/Supporting Files/Info.plist";
555 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
556 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.VoiceMemoTests;
557 | PRODUCT_NAME = "$(TARGET_NAME)";
558 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VoiceMemo.app/VoiceMemo";
559 | };
560 | name = Release;
561 | };
562 | 612545501C2D8AB5006281D2 /* Debug */ = {
563 | isa = XCBuildConfiguration;
564 | buildSettings = {
565 | INFOPLIST_FILE = "VoiceMemoUITests/Supporting Files/Info.plist";
566 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
567 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.VoiceMemoUITests;
568 | PRODUCT_NAME = "$(TARGET_NAME)";
569 | TEST_TARGET_NAME = VoiceMemo;
570 | USES_XCTRUNNER = YES;
571 | };
572 | name = Debug;
573 | };
574 | 612545511C2D8AB5006281D2 /* Release */ = {
575 | isa = XCBuildConfiguration;
576 | buildSettings = {
577 | INFOPLIST_FILE = "VoiceMemoUITests/Supporting Files/Info.plist";
578 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
579 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.VoiceMemoUITests;
580 | PRODUCT_NAME = "$(TARGET_NAME)";
581 | TEST_TARGET_NAME = VoiceMemo;
582 | USES_XCTRUNNER = YES;
583 | };
584 | name = Release;
585 | };
586 | /* End XCBuildConfiguration section */
587 |
588 | /* Begin XCConfigurationList section */
589 | 612545191C2D8AB5006281D2 /* Build configuration list for PBXProject "VoiceMemo" */ = {
590 | isa = XCConfigurationList;
591 | buildConfigurations = (
592 | 612545471C2D8AB5006281D2 /* Debug */,
593 | 612545481C2D8AB5006281D2 /* Release */,
594 | );
595 | defaultConfigurationIsVisible = 0;
596 | defaultConfigurationName = Release;
597 | };
598 | 612545491C2D8AB5006281D2 /* Build configuration list for PBXNativeTarget "VoiceMemo" */ = {
599 | isa = XCConfigurationList;
600 | buildConfigurations = (
601 | 6125454A1C2D8AB5006281D2 /* Debug */,
602 | 6125454B1C2D8AB5006281D2 /* Release */,
603 | );
604 | defaultConfigurationIsVisible = 0;
605 | defaultConfigurationName = Release;
606 | };
607 | 6125454C1C2D8AB5006281D2 /* Build configuration list for PBXNativeTarget "VoiceMemoTests" */ = {
608 | isa = XCConfigurationList;
609 | buildConfigurations = (
610 | 6125454D1C2D8AB5006281D2 /* Debug */,
611 | 6125454E1C2D8AB5006281D2 /* Release */,
612 | );
613 | defaultConfigurationIsVisible = 0;
614 | defaultConfigurationName = Release;
615 | };
616 | 6125454F1C2D8AB5006281D2 /* Build configuration list for PBXNativeTarget "VoiceMemoUITests" */ = {
617 | isa = XCConfigurationList;
618 | buildConfigurations = (
619 | 612545501C2D8AB5006281D2 /* Debug */,
620 | 612545511C2D8AB5006281D2 /* Release */,
621 | );
622 | defaultConfigurationIsVisible = 0;
623 | defaultConfigurationName = Release;
624 | };
625 | /* End XCConfigurationList section */
626 |
627 | /* Begin XCVersionGroup section */
628 | 612545281C2D8AB5006281D2 /* VoiceMemo.xcdatamodeld */ = {
629 | isa = XCVersionGroup;
630 | children = (
631 | 612545291C2D8AB5006281D2 /* VoiceMemo.xcdatamodel */,
632 | );
633 | currentVersion = 612545291C2D8AB5006281D2 /* VoiceMemo.xcdatamodel */;
634 | path = VoiceMemo.xcdatamodeld;
635 | sourceTree = "";
636 | versionGroupType = wrapper.xcdatamodel;
637 | };
638 | /* End XCVersionGroup section */
639 | };
640 | rootObject = 612545161C2D8AB5006281D2 /* Project object */;
641 | }
642 |
--------------------------------------------------------------------------------