iOS 12강 Apple Music App

린다·2021년 3월 12일
0

iOS beginner

목록 보기
13/14
post-thumbnail
  • 화면은 총 두 화면
    → main 화면 (collectionView)
    → player 화면
  • AVFoundation 미디어 관련 프레임 워크
    1) 카메라 어플 (Snow)
    2) 비디오 스트리밍 앱 (Netflix)
    3) 음악 앱 (Spotify)
  • AVPlayer 미디어 플레이 시켜주는 객체
  • CollectionReusableView 섹션 헤더뷰에 사용

개발 진행 단계

main화면 구성

  1. CollectionView 삽입
  2. CollectionViewCell을 custom cell로 사용하기 위해서 필요한 component 넣어줌
  3. class, reuse identifier 설정해줌
  4. collectionView와 datasource, delegate 연결해줌
  5. 프로토콜 구현
collectionview protocol

extension HomeViewController: UICollectionViewDataSource
//몇 개 표시할까
numberOfItemsInSection -> return trackManager.tracks.count
//셀 어떻게 표시할까
//헤더뷰 표시 어떻게 할까
extension HomeViewController: UICollectionViewDelegate
//클릭했을 때 어떻게 할까
extension HomeViewController: UICollectionViewDelegateFlowLayout
//셀 사이즈 어떻게 할까

AVFoundation
애플의 미디어 프레임워크. 여러 미디어 처리 작업(편집, 미디어 캡쳐 등등)을 제공한다. 이러한 기본적인 기능을 넘어 보다 심화된 기능까지 제공한다. 미디어의 제목, 음원 제공자 등과 같은 요소를 담고 있는 메타데이터에 접근이 가능하며 자막을 표시하는 등의 기능이 존재한다. 또한 재생 중인 미디어에 대해 실시간으로 처리해야할 작업들에 대해서도 관련 기능을 제공한다.

주요 기능들 또한 asset을 재생하고 관리하는 것과 연결돼있음. AVAsset 클래스는 미디어 데이터에 관한 정적인 정보를 담는 클래스로 local file based 미디어 데이터나 리모트에 위치한 데이터 모두 URL로 객체화할 수 있음.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/41eacd37-9b84-48e6-af6d-357b88b59ba1/Untitled.png

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/dc201de7-ce2d-401f-b782-60cfe5f3d76c/Untitled.png

https://baked-corn.tistory.com/118


b가 nil일때 대신할 값을 지정

let a = b ?? c

옵셔널값의뒤에 물음표 두개를 붙이면 b가 nil일때 c를 a에 대신넣는다 라는게 됩니다. 물론 c와 b의 원래값의 타입은 같아야합니다.


urls(forResourcesWithExtension: "mp3", subdirectory: nil)

//정석
func urls(forResourcesWithExtension ext: String?, subdirectory subpath: String?) 
-> [URL]?
  • 언급된 특정한 subdirectory에 있는 특정한 확장자 파일들의 url을 배열로 리턴해주는 함수
  • subpath가 nil인 경우에는 최상위 non-localized resource directory와 any language-specific directories에서 검색함(iOS에서는 최상위 non-localized resource directory가 main bundle directory임

//map과 관련되어
let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let lowercaseNames = cast.map { $0.lowercased() }
// 'lowercaseNames' == ["vivien", "marlon", "kim", "karl"]
let letterCounts = cast.map { $0.count }
// 'letterCounts' == [6, 6, 3, 4]
//compactMap -> non-optional object들을 받기 위해서 사용
//그냥 map은 optional이 있을수도 있나봄?

let possibleNumbers = ["1", "2", "three", "///4///", "5"]

let mapped: [Int?] = possibleNumbers.map { str in Int(str) }
// [1, 2, nil, nil, 5]

let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }
// [1, 2, 5]

//dictionary grouping by

let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]
let studentsByLetter = Dictionary(grouping: students, by: { $0.first! })
// ["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]

PlayerView

  1. View 구성 후 아울렛 연결
  2. SimplePlayer 싱글톤 패턴 작성

싱글톤(Singleton) 패턴이란?

특정 객체가 앱에서 유일하게 하나만 존재하여 다른 객체들이 그 안의 내용을 공유하는 방식이다.

class Singleton {
		static let shared = Singleton()
		var x = 0
}

클래스를 정의할 때 내부에 해당 클래스와 같은 타입의 타입 프로퍼티를 생성하여 객체를 생성하지 않아도 접근이 가능하도록 한다.

이러한 패턴을 따르는 클래스는 생성자가 여러 번 호출되더라도 실제로 생성되는 객체는 하나이다. 최초 한 번만 메모리를 할당하고(Static) 그 메모리에 인스턴스를 만들어 사용한다.

특정용도로 생성해둔 객체에 정보를 넣어두고 여러 객체에서 접근 가능하도록 하여 데이터를 사용한다. 주로 프로그램이 실행되고 끝날 때까지 메모리에 유지된다.


  • 현재 시간(초 단위) 구하는 법 (currentTime)
AVPlayer.currentItem?.currentTime() //여기까지는 CMTime형식이기 때문에 seconds를 통해서 double로 바꿔줌
AVPlayer.currentItem?.currentTime().seconds ?? 0
  • 재생, 멈춤, seek
    : 모두 AVPlayer를 통해 바로 실행할 수 있음
player.play()
player.pause()
player.seek(to: time)
//이때 time은 CMTime임
  • replace current item → 재생할 아이템을 변경할 수 있도록 해줌
//내장함수
player.replaceCurrentItem(with: item)
이때 item은 AVPlayerItem
  • 클릭했을 때 다른 스토리보드 띄우기
// 1.원하는 스토리보드 가져오기
let playerStoryboard = UIStoryboard.init(name: "Player", bundle: nil)
// 2.가져온 스토리보드에서 특정 view controller 꺼내기
guard let playerVC = playerStoryboard.instantiateViewController(identifier:
"PlayerViewController") as? PlayerViewController else {return}
// 3.내가 누른 아이템의 index Path를 통해 trackMangager한테 아이템 정보 받아옴
let item = trackManager.tracks[indexPath.item]
// 4.가져온 아이템을 해당 ViewController의 currentItem으로 바꿔줌
playerVC.simplePlayer.replaceCurrentItem(with: item)
// 5.뷰컨트롤러 띄워주기
present(playerVC, animated: true, completion: nil)

→ 이건 collection view에 있는 아이템들이라서 header도 따로 코드 수정해줘야함

//header.tapHandler 안의 코드
//header가 탭 됐을 때 발동하는 closure, 내용은 동일함

여기까지 했는데 아이템이 안뜨는 이유~는~ playerviewcontroller를 아직 수정안해줬기 때문이지~

PlayerViewController 수정하기!

//트랙정보 업데이트하기
//클릭됐을 때 simplePlayer를 통해서 current item을 바꿔줬기 때문에 그 item을 그대로 가져오면 됨
//1. 현재 currentItem 가져오기 -> 이때 track으로 convert해주는거 까먹지 말기!!
guard let track = simplePlayer.currentItem?.convertToTrack() else {return}
//2. 그다음은 그냥 쓱쓱 바꿔주기~~
thumbnailImageView.image = track.artwork
        titleLabel.text = track.title
        artistLabel.text = track.artist

이제 콜렉션 뷰 눌렀을 때 플레이어 뷰는 뜬다^^


  1. timeinfo 업데이트해주기
currentTimeLable.text = secondsToString(sec: simplePlayer.currentTime)
totalDurationLable.text = secondsToString(sec: simplePlayer.totalDurationTime)

//+seeking 안하고 있을 때는 slider가 계속 올라갸아하기 때문에(seeking할때는 멈춰있어야한ㄴ다는 의미도 됨)
timeSlider.value = Float(simplePlayer.currentTime/simplePlayer.totalDurationTime)
  1. Timeobserver 수정하기
//재생되는 녀석의 시간을 관찰해야함. 지금 몇 분 어디를 재생하고 있는가~
timeObserver = simplePlayer.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 10), queue: DispatchQueue.main, using: { time in
            //해당하는 곡의 현재시간을 넘겨주고있음
            self.updateTime(time: time)
        }
  1. togglePlayButton
if simplePlayer.isPlaying {
            simplePlayer.pause()
        } else {
            simplePlayer.play()
        }
        updatePlayButton()
}
  1. updatePlayButton
if simplePlayer.isPlaying {
            //configuration: 사이즈 조절
            let configuration = UIImage.SymbolConfiguration(pointSize: 40)
            let image = UIImage(systemName: "pause.fill", withConfiguration: configuration)
            playControlButton.setImage(image, for: .normal)
        } else {
            let configuration = UIImage.SymbolConfiguration(pointSize: 40)
            let image = UIImage(systemName: "play.fill", withConfiguration: configuration)
            playControlButton.setImage(image, for: .normal)
        }
  1. seeking 구현
guard let currentItem = simplePlayer.currentItem else {return}
        
let position = Double(sender.value)
let seconds = position * currentItem.duration.seconds
let time = CMTime(seconds: seconds, preferredTimescale: 100)
simplePlayer.seek(to: time)

0개의 댓글

관련 채용 정보