@IBOutlet weak var pvProgressPlay: UIProgressView!
var audioPlayer : AVAudioPlayer!
var audioFile : URL!
let MAX_VOLUME : Float = 10.0
var progressTimer : Timer!
// 플레이 타임을 프로그레이스바로 표현
let timePlayerSelector : Selector = #selector(ViewController.updatePlaytime)
3. 파일 위치 설정하고, play 설정값을 세팅 후, audioPlayer.play() 호출
4. Record가 되지 않으면, adioRecord.record()의 반환값이 Bool 이므로, 반환값을 출력해 보면 됨.
전체 소스는 아래와 같습니다.(Record 포함)
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate, AVAudioRecorderDelegate {
@IBOutlet weak var pvProgressPlay: UIProgressView!
@IBOutlet weak var lblCurrentTime: UILabel!
@IBOutlet weak var lblEndTime: UILabel!
@IBOutlet weak var btnPlay: UIButton!
@IBOutlet weak var btnPause: UIButton!
@IBOutlet weak var btnStop: UIButton!
@IBOutlet weak var slVolume: UISlider!
@IBOutlet weak var swMode: UISwitch!
@IBOutlet weak var lblMode: UILabel!
@IBOutlet weak var btnRecord: UIButton!
@IBOutlet weak var lblRecordTime: UILabel!
var audioPlayer : AVAudioPlayer!
var audioFile : URL!
let MAX_VOLUME : Float = 10.0
var progressTimer : Timer!
let timePlayerSelector : Selector = #selector(ViewController.updatePlaytime)
let timeRecordSelector : Selector = #selector(ViewController.updateRecordtime)
var audioRecorder : AVAudioRecorder!
var isRecordMode = false
override func viewDidLoad() {
super.viewDidLoad()
selectAudioFile()
if !isRecordMode {
initPlay()
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
}else {
initRecord()
}
// Do any additional setup after loading the view.
}
func selectAudioFile() {
if !isRecordMode {
audioFile = Bundle.main.url(forResource: "Helium", withExtension: "mp3")
initPlay()
} else {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
audioFile = documentDirectory.appendingPathComponent("recordFile.m4a")
}
}
func initRecord() {
let recordSettings = [
AVFormatIDKey : NSNumber(value: kAudioFormatAppleLossless as UInt32),
AVEncoderAudioQualityKey : AVAudioQuality.max.rawValue,
AVEncoderBitRateKey : 320000,
AVNumberOfChannelsKey : 2,
AVSampleRateKey : 44100.0 ] as [String : Any]
do {
audioRecorder = try AVAudioRecorder(url: audioFile, settings: recordSettings)
} catch let error as NSError {
print("************ ERROR-initRecord : \(error)")
}
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
slVolume.value = 1.0
audioPlayer.volume = slVolume.value
lblEndTime.text = convertNSTimeInterval2String(0)
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(false, false, false)
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSession.Category.playAndRecord)
} catch let error as NSError {
print("************ ERROR-setCategory : \(error)")
}
do {
try session.setActive(true)
} catch let error as NSError {
print("ERROR-setActive : \(error)")
}
}
func initPlay() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFile)
} catch let error as NSError {
print("Error-initPlay : \(error)")
}
slVolume.maximumValue = MAX_VOLUME
slVolume.value = 1.0
pvProgressPlay.progress = 0
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.volume = slVolume.value
lblEndTime.text = convertNSTimeInterval2String(audioPlayer.duration)
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(true, false, false)
}
func convertNSTimeInterval2String(_ time:TimeInterval) -> String{
let min = Int(time/60)
let sec = Int(time.truncatingRemainder(dividingBy: 60))
let strTime = String(format: "%02d:%02d", min, sec)
return strTime
}
func setPlayButtons(_ play:Bool , _ pause:Bool, _ stop:Bool) {
btnPlay.isEnabled = play
btnPause.isEnabled = pause
btnStop.isEnabled = stop
}
@IBAction func btnPlayAudio(_ sender: UIButton) {
setPlayButtons(false, true, true)
audioPlayer.play()
progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timePlayerSelector, userInfo: nil, repeats: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@objc func updatePlaytime() {
lblCurrentTime.text = convertNSTimeInterval2String(audioPlayer.currentTime)
pvProgressPlay.progress = Float(audioPlayer.currentTime / audioPlayer.duration)
print(audioPlayer.currentTime.description + ", " + audioPlayer.duration.description)
}
@IBAction func btnPauseAudio(_ sender: UIButton) {
setPlayButtons(true, false, true)
audioPlayer.pause()
}
@IBAction func btnStopAudio(_ sender: UIButton) {
setPlayButtons(true, false, false)
pvProgressPlay.progress = 0
audioPlayer.stop()
audioPlayer.currentTime = 0
lblCurrentTime.text = convertNSTimeInterval2String(0)
progressTimer.invalidate()
}
@IBAction func slChangeVolume(_ sender: UISlider) {
audioPlayer.volume = slVolume.value
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
progressTimer.invalidate()
setPlayButtons(true, false, false)
}
@IBAction func swChangeMode(_ sender: UISwitch) {
if sender.isOn{
audioPlayer.stop()
audioPlayer.currentTime = 0
lblRecordTime!.text = convertNSTimeInterval2String(0)
isRecordMode = true
btnRecord.isEnabled = true
lblRecordTime.isEnabled = true
}else {
isRecordMode = false
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
lblRecordTime.text = convertNSTimeInterval2String(0)
}
selectAudioFile()
if !isRecordMode {
initPlay()
} else {
initRecord()
}
}
@IBAction func btnRecord(_ sender: UIButton) {
//if ( sender as AnyObject).titleLabel?.text == "Record" {
if sender.titleLabel?.text == "Record" {
var isRecording : Bool = audioRecorder.record()
print("IS RECORDING? : ", isRecording.description)
//(sender as AnyObject).setTitle("Stop", for: UIControl.State())
sender.setTitle("Stop", for: UIControl.State())
progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timeRecordSelector, userInfo: nil, repeats: true)
} else {
audioRecorder.stop()
progressTimer.invalidate()
(sender as AnyObject).setTitle("Record", for: UIControl.State())
btnPlay.isEnabled = true
initPlay()
}
}
@objc func updateRecordtime() {
lblRecordTime.text = convertNSTimeInterval2String(audioRecorder.currentTime)
}
}
Exception NSException *
"[<UIViewController 0x103a17520> setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key btnSize." 0x0000600000c9dfe0
위와 같이 실행구문은 변함이 없으나, 함수 호출을 정의하는 부분이 많이 축약(함수명, 파라미터의 자료형 등) 됩니다.
사실 클로져에 대한 활용은 많은 예시문을 별도로 보관하고 계시다가 발췌해서 이용하거나, 기존 것을 활용해서 수정해서 사용하시면서 익히시는 것이 많은 도움이 될 것입니다. 특히, 집합 관련하여 filter 등을 활용한 예제들은 소스코드를 많이 간결하게 만들어주기 때문에 꼭 한번 찾아 보세요.
개발 과정에서 기본으로 제공되는 첫화면, Main.storyboard 파일명을 변경할 수 있습니다.
스토리보드 파일 속성 창에서 "Is initial View Controller" 속성 값을 체크하면 될 것 같지만 사실 추가 변경작업을 해줘야 합니다.
파일명을 변경했는데, NSBundle 파일에서 Main이라는 storyboard파일이 없다는 오류가 발생합니다.
NetflixClone[52739:34390677]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: 'Could not find a storyboard named 'Main' in bundle NSBundle
</Users/abdurl/Library/Developer/CoreSimulator/Devices/
B70278F4-87BD-46B7-984B-B63EDE4CEBFC/data/Containers/Bundle/Application/
AE7F576F-36A8-446D-B54F-87C874367AD4/NeflixClone.app> (loaded)'
*** First throw call stack:
(해결방안) 프로젝트 설정 파일 수정(3군데)
1. Main storyboard file base name
2. Application Secene Manifest - Scene Configuration - item 0 - Storyboard Name
3. Info.plist Values - UIKit Main Storyboard File Base Name
3군데를 모두 변경 후에도 인식하지 못한다면, XCODE를 재실행하여 캐시를 삭제하면 제대로 인식할 수 있습니다.
Swift 개발에서 RestApi를 호출하여 정보를 받아오는 경우가 많습니다. 그런 경우, 변수명 네이밍 규칙이 본인의 규칙과 다를 경우 CodingKeys를 이용하여 RestApi 명세의 항목과 개발하는 클래스(스트럭처)의 항목을 매핑하여 개발할 수 있습니다.
이때 주의 사항입니다.
struct MovieResult : Codable {
let trackName : String?
let previewUrl : String?
let artworkUrl : String?
let releaseDate : String?
let shortDescription : String?
let longDescription : String?
enum CodingKeys : String, CodingKey {
case trackName
case previewUrl
case artworkUrl = "artworkUrl100"
case releaseDate
case shortDescription
case longDescription
}
}
response값에 특정항목이 없을 경우를 대비하여 String? , Int? 형태로 nil 을 대비한다
response항목의 이름을 다르게 사용할 경우, enum CodingKeys : String, CodingKey{ ... } 를 정의한다.
enum CodingKeys에 모든 항목을 정의해야 한다. 누락시 오류 발생. 이름을 다르게 사용하고 싶은 항목만 정의(예시:artworkUrl)
만약, enum CodingKeys에 항목을 누락하거나 오타를 치게 되면, 아래와 같이 오류가 발생한다.
Type *** does not conform to protocol 'Decodable'
Type *** does not conform to protocol 'Encodable'