탭-터치 이벤트에 대응하는 카운트 예제 코드

 

아래와 같이 3개의 함수를 재정의 하면 됩니다. 탭과 터치의 차이는 연속해서 클릭(터치)하냐?의 차이입니다.

  1. override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)  
  2. override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) 
  3. override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)  

전체소스는 아래와 같습니다.

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var lblMessage: UILabel!
    @IBOutlet weak var lblTapCount: UILabel!
    @IBOutlet weak var lblTouchCount: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first! as UITouch
        lblMessage.text = "Touch Began"
        lblTapCount.text = String(touch.tapCount)
        lblTouchCount.text = String(touches.count)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first! as UITouch
        lblMessage.text = "Touch Moved"
        lblTapCount.text = String(touch.tapCount)
        lblTouchCount.text = String(touches.count)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first! as UITouch
        lblMessage.text = "Touch Ended"
        lblTapCount.text = String(touch.tapCount)
        lblTouchCount.text = String(touches.count)
    }


}
Posted by 목표를 가지고 달린다
,

동영상 플레이를 만드는 순서는 아래와 같습니다. 단순하기 때문에 쉽게 작성할 수있습니다. 
다만, 유투브 영상을 플레이하는 것이라면 어렵습니다. 
왜냐면, 유투브 동영상의 주소는 동영상(*.mp4)을 포함하는 html 문서이기 때문입니다. 

그래서 유투브 영상을 플레이하고 싶으시면, mp4 등으로 다운받아 다른 서버에 올린 후, 해당 url 을 확인하셔서 이용해야 합니다. 

<< 코딩 순서 >>

  1. import AVKit 을 포함합니다.
  2. url 을 선언합니다. (내부와 외부에 따라 다름)
  3. AVPlayer(url : url as URL)을 player로 선언합니다.
  4. AVPlayerController.player 에 위의 player를 대입합니다.
  5. player.play() 합니다.

전체소스는 아래와 같습니다.

import UIKit
import AVKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }


    @IBAction func btnInternalMediaPlay(_ sender: UIButton) {
        let filePath:String? = Bundle.main.path(forResource: "mov", ofType: "MOV")
        let url = NSURL(fileURLWithPath: filePath!)
        
        playVideo(url: url)
    }
    @IBAction func btnExternalMediaPlay(_ sender: UIButton) {
        
        let url = NSURL(string: "https://dl.dropboxusercontent.com/s/e38auz050w2mvud/Fireworks.mp4")!
        
        playVideo(url: url)
    }
    
    func playVideo(url : NSURL) {
        let playerController = AVPlayerViewController()
        
        let player = AVPlayer(url: url as URL)
        playerController.player = player
        
        self.present(playerController, animated: true) {
            player.play()
        }
    }
}

Posted by 목표를 가지고 달린다
,

Swift에서 오디오를 호출하는 예제 소스

Swift 오디오 예제 화면

1. import AVFoundation 관련 라이브러리 참조

2. 변수 선언

@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)
    }
    
    
}
Posted by 목표를 가지고 달린다
,

this class is not key value coding-compliant for the key btnSize

책 예제를 따라하거나, 이미 만들어진 ViewController와 mainstory.swift를 합칠 경우,

내부 Widget에 정의한 action에 대해 연결이 깨진 경우가 발생한다.

확인하려면, Xcode에서 Triggered Segues에 링크가 깨져서 노란색으로 경고를 띄워주는 것을 발견할 수 있다. 이것을 다시 만들어서 정의해 주면 된다. 

Swift 오류 확인하는 화면

오류 내용은 아래와 같이 나타난다. 

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIViewController 0x103a17520> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key btnSize.'
*** First throw call stack:
(
	0   CoreFoundation                      0x00000001804b910c __exceptionPreprocess + 172
	1   libobjc.A.dylib                     0x0000000180092da8 objc_exception_throw + 72
	2   CoreFoundation                      0x00000001804b8ca0 -[NSException init] + 0
	3   Foundation                          0x0000000180e9da3c -[NSObject(NSKeyValueCoding) setValue:forKey:] + 268
	4   UIKitCore                           0x00000001853b7dc4 -[UIViewController setValue:forKey:] + 76
	5   UIKitCore                           0x00000001857435ec -[UIRuntimeOutletConnection connect] + 80
	6   CoreFoundation                      0x00000001804a5cf0 -[NSArray makeObjectsPerformSelector:] + 192
	7   UIKitCore                           0x0000000185736ee4 -[UINib instantiateWithOwner:options:] + 1420
    8   UIKitCore                           0x00000001853bf7a8 -[UIViewController loadView] + 392
	9   UIKitCore                           0x00000001853bfa64 -[UIViewController loadViewIfRequired] + 152
	10  UIKitCore                           0x00000001853c0000 -[UIViewController view] + 20
	11  UIKitCore                           0x00000001852f4628 -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:] + 912
	12  UIKitCore                           0x00000001852eef70 -[UITabBarController _setSelectedViewController:performUpdates:] + 352
	13  UIKitCore                           0x00000001852eedd4 -[UITabBarController setSelectedViewController:] + 80
	14  UIKitCore                           0x00000001852f3a98 -[UITabBarController _setSelectedViewControllerAndNotify:] + 216
	15  UIKitCore                           0x00000001852f3964 -[UITabBarController _tabBarItemClicked:] + 160
	16  UIKitCore                           0x0000000185b36e3c -[UIApplication sendAction:to:from:forEvent:] + 96
	17  UIKitCore                           0x00000001850f969c -[UITabBar _sendAction:withEvent:] + 380
	18  UIKitCore                           0x0000000185b36e3c -[UIApplication sendAction:to:from:forEvent:] + 96
	19  UIKitCore                           0x000000018540c830 -[UIControl sendAction:to:forEvent:] + 108
	20  UIKitCore                           0x000000018540cb74 -[UIControl _sendActionsForEvents:withEvent:] + 268
	21  UIKitCore                           0x00000001850fbfb0 -[UITabBar _buttonUp:] + 96
	22  UIKitCore                           0x0000000185b36e3c -[UIApplication sendAction:to:from:forEvent:] + 96
	23  UIKitCore                           0x000000018540c830 -[UIControl sendAction:to:forEvent:] + 108
	24  UIKitCore                           0x000000018540cb74 -[UIControl _sendActionsForEvents:withEvent:] + 268
	25  UIKitCore                           0x000000018540b80c -[UIControl touchesEnded:withEvent:] + 392
	26  UIKitCore                           0x0000000185b6aa10 -[UIWindow _sendTouchesForEvent:] + 972
	27  UIKitCore                           0x0000000185b6be20 -[UIWindow sendEvent:] + 2840
	28  UIKitCore                           0x0000000185b4b80c -[UIApplication sendEvent:] + 376
	29  UIKitCore                           0x0000000185bd5c70 __dispatchPreprocessedEventFromEventQueue + 1156
	30  UIKitCore                           0x0000000185bd8c00 __processEventQueue + 5592
	31  UIKitCore                           0x0000000185bd0f10 updateCycleEntry + 156
	32  UIKitCore                           0x00000001850a5cec _UIUpdateSequenceRun + 76
	33  UIKitCore                           0x0000000185a60858 schedulerStepScheduledMainSection + 168
	34  UIKitCore                           0x0000000185a5fc90 runloopSourceCallback + 80
	35  CoreFoundation                      0x000000018041d294 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
	36  CoreFoundation                      0x000000018041d1dc __CFRunLoopDoSource0 + 172
	37  CoreFoundation                      0x000000018041c940 __CFRunLoopDoSources0 + 232
	38  CoreFoundation                      0x0000000180416e84 __CFRunLoopRun + 788
	39  CoreFoundation                      0x00000001804166f4 CFRunLoopRunSpecific + 552
	40  GraphicsServices                    0x00000001905e5b10 GSEventRunModal + 160
	41  UIKitCore                           0x0000000185b319dc -[UIApplication _run] + 796
	42  UIKitCore                           0x0000000185b35bd4 UIApplicationMain + 124
	43  UIKitCore                           0x0000000184f0a334 block_destroy_helper.22 + 9660
	44  Tab.debug.dylib                     0x0000000102f0103c $sSo21UIApplicationDelegateP5UIKitE4mainyyFZ + 120
	45  Tab.debug.dylib                     0x0000000102f00fb4 $s3Tab11AppDelegateC5$mainyyFZ + 44
	46  Tab.debug.dylib                     0x0000000102f010b8 __debug_main_executable_dylib_entry_point + 28
	47  dyld                                0x0000000102479410 start_sim + 20
	48  ???                                 0x00000001025ca154 0x0 + 4334592340
	49  ???                                 0x9874000000000000 0x0 + 10985405391063482368
)
libc++abi: terminating due to uncaught exception of type NSException
Exception	NSException *	
"[<UIViewController 0x103a17520> setValue:forUndefinedKey:]: 
this class is not key value coding-compliant for the key btnSize."	0x0000600000c9dfe0

 

Posted by 목표를 가지고 달린다
,

익명함수(클로져, Closure) 이해하기

  함수 비고
함수원본 func 함수명 ( 파라미터명 : 자료형) -> (반환자료형) {
             실행 구문
}
보통함수
1 { ( 파라미터명 : 자료형) -> (반환타입) in 실행 구문 } 익명함수
2 { (파라미터명) in 실행 구문 }
3 { 파라미터명 in 실행 구문 }

위의 방식을 이용한 아래의 예제를 보시면서 이해해보세요.

  함수 비고
함수원본 func completeWork(completed : Bool) -> () {
    print ("complete : \(completed)") 
보통함수
1 { (completed: Bool) -> () in print ("complete : \(completed)")  } 익명함수

2 { (completed: Bool)         in print ("complete : \(completed)")  }
3 { (completed)                  in print ("complete : \(completed)")  }
4 { completed                    in print ("complete : \(completed)")  }

위와 같이 실행구문은 변함이 없으나, 함수 호출을 정의하는 부분이 많이 축약(함수명, 파라미터의 자료형 등) 됩니다.

사실 클로져에 대한 활용은 많은 예시문을 별도로 보관하고 계시다가 발췌해서 이용하거나, 기존 것을 활용해서 수정해서 사용하시면서 익히시는 것이 많은 도움이 될 것입니다. 특히, 집합 관련하여 filter 등을 활용한 예제들은 소스코드를 많이 간결하게 만들어주기 때문에 꼭 한번 찾아 보세요.

Posted by 목표를 가지고 달린다
,

시간 단축을 위해,  노션을 사용한다면 자주 사용할 만한 단축키를 추천해요

작성 중인 블록 선택 "Esc"
제목 1(가장 큰 제목) "#" 입력 후, "스페이스 바"
제목 2(중간 크기 제목) "#", "#" 입력 후, "스페이스 바"
제목 3(가장 작은 제목) "#", "#", "#" 입력 후, "스페이스 바"
인용 형식 ' " ' 입력 후, "스페이스 바"
리스트 생성 "*" 도는 "+" 또는 "-" 누른 후, "스페이스 바"
번호 생성 "1"과 "." 누른 후, "스페이스 바"
토글 생성 ">" 누른 후, "스페이스 바"
체크박스 생성 "[ ]" + "스페이스 바"
이모티콘 삽입!!!!!!(맥북) "Ctrl 키"와 "cmd키" 누르면서, "스페이스 바" 
특정 이모티콘 삽입 " : "  + "특정 단어" 을 입력시 해당 이모티콘 생성
구분선 복사 등 항목을 누른 상태에서, "Alt키"(맥은 "옵션키")를 눌러 드래그 & 드랍
글자색 또는 배경색 / 각 생삭의 영문명
구분선 "-", "-", "-" 
(편집) 굵게 처리 "Ctrl 키" 누른 후, "B" (Bold)
최근 사용한 명령어(제목 설정, 편집 설정 등) "Ctrl 키" + "Shift 키" + "H"를 동시에 누르면 재수행
강조하는 문구 (큰 박스 포함) - 콜아웃 /callout   

처음 노션을 사용하거나 입문자일 경우, 기본적인 단축키 몇개만 사용하더라도 생각보다 엄청난 시간 효율이 있으니 익숙해질 때까지 노력해보시면 좋은 결과가 있을 겁니다. 화이팅!

1. 많은 것을 시도하는 것보단 몇개라도 손에 익숙해지는 것이 중요합니다.!

윈도우와 맥북의 단축키 차이..

2.  / 로 적용할 수 없는 서식

  • ** 텍스트 ** : 굵게 처리
  • *텍스트*  : 기울임꼴 처리
  • ~텍스트  : 취소선 처리
  • 텍스트를 선택한 후, (Ctrl, 맥은 command) + B : 굵게 처리
  • 텍스트를 선택한 후, (Ctrl, 맥은 command) + I  : 기울임꼴 처리
  • 텍스트를 선택한 후, (Ctrl, 맥은 command) + (Shitft) + S : 취소선 처리
  • 텍스트를 선택한 후, (Ctrl, 맥은 command) + E : 인라인 처리 (일정 텍스트만 박스에 담기)

요약하며, 

보기 좋은 문서가 읽기 좋습니다. 가독성 높습니다. 이해가 잘 됩니다.

 

 

Posted by 목표를 가지고 달린다
,

개발 과정에서 기본으로 제공되는 첫화면, Main.storyboard 파일명을 변경할 수 있습니다. 

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

Application Secene Manifest - Scene Configuration - item 0 - Storyboard Name

3. Info.plist Values - UIKit Main Storyboard File Base Name

3군데를 모두 변경 후에도 인식하지 못한다면, XCODE를 재실행하여 캐시를 삭제하면 제대로 인식할 수 있습니다. 

 

Posted by 목표를 가지고 달린다
,

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'

type does not conform to protocol Decodable, Encodable

 

Posted by 목표를 가지고 달린다
,