멀고도 험난한 개발 일지

이런저런_구글링_1 - Date to String, Design Pattern, Voice Recorder, Post Method

이드entity 2022. 6. 11. 21:34
반응형

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

 

반응형