Date to String
기본적으로 Date와 String 사이에서 뭔갈 하려면 Foundation 프레임워크에서 DateFormatter를 사용!
1. Date형을 String형으로 바꾸려면 먼저 Date형 변수를 만들어야지..!
import Foundation
let date = Date()
2. Date를 내가 원하는 형태로 Format 해주는 DateFormatter()를 사용하자
let dateFormatter = DateFormatter()
3. Date가 String으로 바뀌면서 어떤 형태로 바꿀지를 지정!
dateFormatter.dateFormat = "YYMMdd"
dateFormat는 다음과 같은 조합을 섞어 만들 수 있다
예시는 작성일 기준으로!
y - 년도 전체 / YY - 년도 뒤에 두 자리
ex) y - 2022 / YY - 22
M - 월을 숫자로 / MMM - 월을 영어 3글자로
ex) M - 6 / MMM - Jun
d - 일을 숫자로
ex) 11
HH - 시를 24시간제로 / hh - 시를 12시간제로
ex) HH - 19 / hh - 07
mm - 분을 숫자로
ex) 02
ss - 초를 숫자로
ex) 31
이를 조합해서 사용하면 아래와 같은 결과가 나온다!
"y, M d" // 2022, 6 11
"YY, MMM d" // 20, Jun 11
"YY, MMM d, hh:mm" // 20, Jun 11, 07:02
"YY, MMM d, HH:mm:ss" // 20, Jun 11, 19:02:31
4. 마지막으로, String으로 변환한다!
let dateSting: String
dateString.string(from: date)
참고 링크: How to Convert a Date to a String In Swift
App Design Pattern
MVC Model
Model - View - Controller 구조
- Controller에 직접 입력
- View - Controller: Many to One
- View는 Controller를 참조하지 않음
- Model은 View를 간접적으로 참조
- View가 최상위 → 다음으로 Controller → Model이 최하위
→ View는 Controller를, Controller는 Model을 확인할 수 있음
MVP Model
Model - View - Presenter 구조
- View에 직접 입력
- View - Presenter: One to One
- View와 Presenter는 서로 알고 있음
- View는 Model과는 연관이 없어 Presenter를 통해 Model을 업데이트 함
- View와 Presenter가 최상위 → 하위에 Model
→ Presenter가 View와 Model 사이의 상호작용을 조정
MVVM Model
Model - View - ViewModel
- View에 직접 입력
- View - ViewModel: Many to One
- ViewModel은 View를 참조하지 않음
- MVC Model에서 Controller가 ViewModel로 변경된 형태
→ ViewModel은 View가 필요로 하는 데이터와 데이터 가공 과정을 조정해줌
참고 링크: MVVM 디자인패턴
Voice Recorder & Player Using MVVM
해당 참고자료에서는 MVVM Model로 코드를 설계했다!
View는 굳이 설명할 필요가 없다고 생각해서 ViewModel 파일만 설명!!
VoiceRecorderVM.swift
import Foundation
import AVFoundation
import SwiftUI
class VoiceRecorderVM: NSObject, ObservableObject, AVAudioPlayerDelegate {
var audioRecorder: AVAudioRecorder!
var audioPlayer: AVAudioPlayer!
@Published var isRecording: Bool = false
@Published var recordingsList = [Recording]()
override init() {
super.init()
}
func startRecording() {
let recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession.setCategory(.playAndRecord, mode: .default)
try recordingSession.setActive(true)
} catch {
print("Can not setup the recording")
}
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let fileName = path.description.appendingPathComponent("CO-Voice: \(Date().toString(dateFormat: "dd-MM-YY 'at' HH:mm:ss")).m4a")
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: fileName, settings: settings)
audioRecorder.prepareToRecord()
audioRecorder.record()
isRecording = true
} catch {
print("Failed to Setup the Recording")
}
}
func stopRecording() {
audioRecorder.stop()
isRecording = false
}
}
상세설명
@Published var isRecording : Bool = false
Recording이 시작되었는지 확인하는 Bool 값이다!
@Published를 사용함으로써 해당 ObservedObject에서 isRecording 변수가 바뀌는지 안바뀌는지 감시..!
@Published var recordingsList = [Recording]()
Model 중 Recording이란 struct를 제작해 녹음물을 Recording형 Array로 저장!
근데 사실 현재 우리 팀이 만들고 있는 앱은 녹음물을 Array형식으로 저장할 필요가 없음 → 왜냐하면 한번에 하나의 녹음파일을 서버에 올릴 것이기 때문! → 뭐 서버에 저장할때는 필요할 수도 있겠다
그래서 저 부분을 그냥 Recording()으로 해야하는데.. 일단은 Array 형식이 편해서 저렇게 두겠다
let fileName = path.appendingPathComponent("live-On : \(Date().toString(dateFormat: "dd-MM-YY 'at' HH:mm:ss")).m4a")
파일 이름이 Unique 해야 하기 때문에, 파일을 제작한 순간의 시간을 파일 이름으로 지정한다!
Date()로 날짜 가져온 뒤, 미리 제작해 둔 toString() 함수로 입력한 Format대로 현재 시간을 변환!
맨 끝에 .m4a 확장자를 붙여주는게 중요한데, 이걸로 어떤 파일이 될지 저장되기 때문인 듯?!
VoiceRecorderVM.swift > fetchAllRecordings()
func fetchAllRecording(){
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryContents = try! FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil)
for i in directoryContents {
recordingsList.append(Recording(fileURL : i, createdAt:getFileDate(for: i), isPlaying: false))
}
recordingsList.sort(by: { $0.createdAt.compare($1.createdAt) == .orderedDescending})
}
상세설명
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
FileManager로 현재 기기의 파일에 접근!
recordingsList.append(Recording(fileURL : i, createdAt:getFileDate(for: i), isPlaying: false))
directoryContents를 돌면서 Recordings Array형인 recordingsList에 각 파일들 추가하기
VoiceRecordingVM.swift > startPlaying() & stopPlaying()
func startPlaying(url : URL) {
let playSession = AVAudioSession.sharedInstance()
do {
try playSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
} catch {
print("Playing failed in Device")
}
do {
audioPlayer = try AVAudioPlayer(contentsOf : url)
audioPlayer.prepareToPlay()
audioPlayer.play()
for i in 0..<recordingsList.count{
if recordingsList[i].fileURL == url{
recordingsList[i].isPlaying = true
}
}
} catch {
print("Playing Failed")
}
}
func stopPlaying(url : URL){
audioPlayer.stop()
for i in 0..<recordingsList.count {
if recordingsList[i].fileURL == url {
recordingsList[i].isPlaying = false
}
}
}
상세설명
func startPlaying(url : URL) {
// ...
}
가져온 url만 재생할 수 있도록 입력값으로 URL형 받기
for i in 0..<recordingsList.count{
if recordingsList[i].fileURL == url{
recordingsList[i].isPlaying = true
}
}
재생하려는 url과 입력받은 url이 일치할 경우에, 그 Recording의 isPlaying을 true로 바꾸어 재생
stopPlaying도 비슷한 이유로 url 받고, iteration 실행!
참고 링크: Audio Recording in SwiftUI MVVM with AVFoundation | An iOS App
서버에 데이터 보내기
참고한 영상에는 서버에 보내기 및 받아오기 모두 설명되어 있지만, 이전에 BeyondUI - 1주차 게시글에 정리해 둔게 있어서 데이터 보내는 것만 정리하겠다!
데이터 업로드 할 URL 지정
guard let url = URL(string: "http://13.124.90.96:8080/") else { return }
URL 객체를 생성할 때에는 항상 예외처리 해주기! 그래서 guard else 문을 썼다
근데 사실 안쓰면 어차피 xcode에서 오류 내주기 때문에 까먹을수가 없긴 하지 ㅎㅎ
Request 생성
var request = URLRequest(url: url)
업로드 할 때와 동일하게 URLRequest를 사용하여 이전에 입력한 url로 request를 보낸다!
생성한 Request의 여러 요소 지정
request.httpMethod = "POST" request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
httpMethod가 어떤 것인지 지정해주는 과정, 현재는 데이터를 보내는 것이기 때문에 POST로 한다
setValue도 마찬가지로 request의 여러 구성 요소를 지정하는 것!
Session 생성
let session = URLSession.shared.dataTask(with: request){ data, request, error in
if let e = error {
print("Error: \(e.localizedDescription)")
} else {
print("Data: \(String(describing: data)), request: \(String(describing:request))")
}
}
session.resume()
Request를 보냈으면 Session을 시작해야지?
대신, uploadTask는 request의 body 부분을 더 쉽게 제공해주고, 데이터 업로드 과정도 더 편해짐!그리고 자주 까먹는... resume().. session을 만들었으면 실행해줘야함!
그 뒤, error가 있다면 어떤 에러인지 보여주고, upload 하는 task 중에서 받아온 데이터와 request가 어떤 것인지 보여주도록 했다!!
URLSession.shared 뒤에 dataTask라고 적어도 되고, uploadTask라고 적어도 된다
참고자료: https://www.youtube.com/watch?v=xsfzGt7k0rI
'멀고도 험난한 개발 일지' 카테고리의 다른 글
UIKit_유투브_신동규 - #2 Pro처럼 UI 디자인하기 (0) | 2022.07.10 |
---|---|
UIKit_유투브_신동규 - #1 Pro처럼 project 시작하기 (0) | 2022.07.10 |
이런저런_구글링_2 - Alamofire, 얼탱이 없는 Moya.. (0) | 2022.06.23 |
BeyondUI - 1주차 (HTTP, REST, URLSession, Codable, JSONDecoder ...) (0) | 2022.06.05 |
Stanford SwiftUI - Lecture 1 (0) | 2022.05.22 |