iOS ์•ฑ ๊ฐœ๋ฐœ ์ข…ํ•ฉ๋ฐ˜/TIL

8์ฃผ์ฐจ_3์ผ์ฐจ_์•ฑ ๊ฐœ๋ฐœ ์‹ฌํ™”_๋™์˜์ƒ ์•ฑ ๋งŒ๋“ค๊ธฐ_TIL

yeggrrr๐Ÿผ 2024. 5. 1. 20:45


<TIL>
์˜ค๋Š˜์€ ๋™์˜์ƒ ์•ฑ ๋งŒ๋“ค๊ธฐ ์‹ค์Šต์„ ํ•ด๋ดค๋‹ค.
๋‚ฎ์—๋Š” ๊ฐ•์˜๋ณด๋ฉด์„œ ๊ณต๋ถ€ํ•˜๊ณ ,
์˜คํ›„์—๋Š” ํ•ด์„ค๊ฐ•์˜ ๋ณด๊ธฐ ์ „์— ๋จผ์ € ๋งŒ๋“ค์–ด๋ดค๋‹ค.
4์‹œ์— Standard๋ฐ˜ ์ˆ˜์—…์„ ๋“ฃ๊ณ ,
์ €๋… ํšŒ์˜ ์ „๊นŒ์ง€ ๋™์˜์ƒ ์žฌ์ƒ ์•ฑ ๋งˆ๋ฌด๋ฆฌํ•˜๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์กŒ๋‹ค.
(์Šคํƒ ๋‹ค๋“œ๋ฐ˜ ์ปฌ๋ ‰์…˜ ๋ทฐ ์—ฐ์Šต์€ ๋‚ด์ผ ํ•  ์˜ˆ์ •)

์ง€๋‚œ ํŒ€ ํ”„๋กœ์ ํŠธ ๋•Œ,
๊ฒ€์ƒ‰ ํŽ˜์ด์ง€๋ฅผ ๋งก์œผ๋ฉด์„œ URLSession์„ ์‚ฌ์šฉํ–ˆ์–ด์„œ
ํฌ๊ฒŒ ์–ด๋ ต์ง€๋Š” ์•Š์•˜๋˜ ๊ฒƒ ๊ฐ™๋‹ค.
(์˜ค๋Š˜์˜ ๋ฐ”๋ณด ์ฐ ํ•˜๋‚˜ ์žˆ์Œ ใ…‹.ใ…‹)

๋‚ด์ผ์€ iOS ์•ฑ ๊ฐœ๋ฐœ ์‹ฌํ™” ๊ฐ•์˜ ์ •๋ฆฌํ•˜๊ณ ,
์˜ค๋Š˜์€ ๋™์˜์ƒ ์•ฑ ๋งŒ๋“ค๊ธฐ ๋‚ด์šฉ ์ •๋ฆฌ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.



AVKit์„ ์‚ฌ์šฉํ•ด์„œ
๋™์˜์ƒ ์žฌ์ƒ ์•ฑ ๋งŒ๋“ค๊ธฐ

 

์ด๋ฒˆ ์‹ฌํ™”๊ฐ•์˜ ๋งˆ๋ฌด๋ฆฌ ๊ณผ์ œ(?)
์š”๊ตฌ์‚ฌํ•ญ์„ README์— ์ •๋ฆฌํ•ด๋ดค๋‹ค.

<github ์ฃผ์†Œ>
(https://github.com/yeggrrr/YeggrrrAVPlayerApp)

 

<์‹ค์Šต์—์„œ ์‚ฌ์šฉํ•œ Dummy API ์ •๋ณด>

[GET]https://gist.githubusercontent.com/poudyalanil/ca84582cbeb4fc123a13290a586da925/raw/14a27bd0bcd0cd323b35ad79cf3b493dddf6216b/videos.json



< ์•ฑ ๊ตฌํ˜„ ์ „, info.plist ์„ค์ • >

 

info.plist - ‘App Transport Security Settings’ ํ•ญ๋ชฉ ์ƒ์„ฑ - ํ•˜์œ„์— ‘Allow Arbitrary Load’ ํ•ญ๋ชฉ ์ƒ์„ฑ ํ›„ ‘Yes’๋กœ ์„ค์ •


 

< Model >

 

struct VideoInfo: Decodable {
    let id: String
    let title: String
    let thumbnailUrl: URL
    let duration: String
    let uploadTime: String
    let views: String
    let author: String
    let videoUrl: URL
    let description: String
    let subscriber: String
    let isLive: Bool
}

VideoInfo ํŒŒ์ผ์„ ํ•˜๋‚˜ ์ƒ์„ฑํ•ด์„œ
dummy API๋กœ ํ•ด๋‹น Response JSON์„ ํ™•์ธํ•˜์—ฌ
๊ทธ ํ˜•์‹์— ๋งž๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค.
(์•ˆ์“ฐ๋Š” ์ •๋ณด๋“ค์€ ๋นผ๋„ ๋˜์ง€๋งŒ, ์šฐ์„  ๋‹ค ๋„ฃ์Œ) 

 



< UI ์„ค์ • >


์•„์ง ์ฝ”๋“œ ๋ฒ ์ด์Šค๊ฐ€ ์ต์ˆ™ํ•˜์ง€ ์•Š์•„์„œ ๋งˆ๋ฌด๋ฆฌ ๊ณผ์ œ๋Š” ์Šคํ† ๋ฆฌ๋ณด๋“œ๋ฅผ ํ™œ์šฉํ–ˆ๋‹ค.
(์–ด์ฐจํ”ผ ์ด๋ฒˆ ๊ฐœ์ธ ๊ณผ์ œ๋Š” ์ฝ”๋“œ ๋ฒ ์ด์Šค..๐Ÿฅน ํ”ผํ•ด๊ฐˆ ์ˆ˜ ์—†์Œ)

Show Library์—์„œ
Table View & Table View Cell & Image View & Label
์„ ์ถ”๊ฐ€ํ•ด์„œ ์˜คํ† ๋ ˆ์ด์•„์›ƒ์„ ์žก์•„์ฃผ์—ˆ๋‹ค.
(์ด์ œ ์Šคํ† ๋ฆฌ๋ณด๋“œ ์˜คํ† ๋ ˆ์ด์•„์›ƒ์€ ๋‚˜๋ฆ„..๋งˆ์Šคํ„ฐ ํ–ˆ์„์ง€๋„..?๐Ÿ˜€)



< Outlet ์—ฐ๊ฒฐ >


โ‘ 
TableViewCell ํŒŒ์ผ ์ƒˆ๋กœ ์ƒ์„ฑํ•ด์ฃผ๊ณ ,
CustomClass์— Class ์„ค์ •ํ•ด์ฃผ๊ธฐ!
Identifier๋„ ์žŠ์ง€๋ง๊ธฐ!

โ‘ก
TableViewCell ์ฝ”๋“œ์— 
ImageView, Label ์•„์šธ๋ › ์—ฐ๊ฒฐ

TableView๋Š” VC ์ฝ”๋“œ์— ์•„์šธ๋ › ์—ฐ๊ฒฐ!

โ‘ข
์ฝ”๋“œ์— static์œผ๋กœ identifier ์ €์žฅํ•ด์ฃผ๊ธฐ
static let identifier = "VideoTableViewCell"
(์ด๋ ‡๊ฒŒ ํ•ด์ฃผ๋ฉด, ๋‚˜์ค‘์— ์“ฐ๊ธฐ ํŽธํ•จ)


< Cell >


์šฐ์„ , ์…€์—์„œ ์ž‘์„ฑํ•ด์ฃผ์–ด์•ผํ•˜๋Š” ๊ฒƒ์€
๋กœ์ง์„ ๊ตฌ์„ฑํ•˜๋Š” ์ธ๋„ค์ผ ์…‹ํŒ…, ํƒ€์ดํ‹€ ์…‹ํŒ…!
์ด ๋‘ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.

    func setThumbnail(imageURL: URL) {
        DispatchQueue.global().async { [ weak self ] in
            if let data = try? Data(contentsOf: imageURL), let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self?.thumbnailImage.image = image
                    }
            } else {
                DispatchQueue.main.async {
                    self?.thumbnailImage.image = UIImage(systemName: "video.fill")
                    self?.thumbnailImage.tintColor = .darkGray
                }
            }
        }
    }


์ด๋ ‡๊ฒŒ setThumbnail ํ•จ์ˆ˜๋กœ imageURL์„ ๋ฐ›์•„์˜ค๊ณ , 

DispatchQueue๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ž‘์—…ํ•ด์ฃผ๊ธฐ!
๊ทธ๋ฆฌ๊ณ  ์œ„์™€ ๊ฐ™์ด imageURL๋ฅผ ๋ฐ์ดํ„ฐํ™” ํ•ด์ฃผ๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
(๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ weak self ์‚ฌ์šฉ)

๊ทธ๋ฆฌ๊ณ  ์ž‘์—…์ด ์ž˜ ์ด๋ฃจ์–ด์กŒ์„ ๋•Œ์—๋Š” main์—์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ๋˜๋„๋ก ํ•ด์ฃผ์—ˆ๋‹ค.
(UI ์ž‘์—…์€ ๋ชจ๋‘ main์—์„œ ํ•ด์ค˜์•ผํ•จ)

๋ฐ˜ํ™˜์ด ๋˜์ง€ ์•Š๊ณ , ์ด๋ฏธ์ง€ URL์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š”
๋นˆ ํ™”๋ฉด์ด ๋‚˜์˜ค์ง€ ์•Š๋„๋ก "Video.fill"์ด๋ผ๋Š” ๋””ํดํŠธ ์ด๋ฏธ์ง€๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

    func setTitle(title: String) {
        self.titleLabel.text = title
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        self.thumbnailImage.image = nil
    }

๋‹ค์Œ์€ ํƒ€์ดํ‹€ ์…‹ํŒ…ํ•ด์ฃผ๊ธฐ!

setTitle ํ•จ์ˆ˜์—์„œ title์ด๋ผ๋Š” ์ธ์ž๋ฅผ ๋ฐ›์•„์˜ค๋„๋ก ๋งŒ๋“ค๊ณ ,
์—ฐ๊ฒฐํ•ด๋…ผ titleLabel์˜ text์— ๋„ฃ์–ด์ฃผ๊ธฐ!

๊ทธ๋ฆฌ๊ณ 
prepareForReuse()๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์žฌ์‚ฌ์šฉ๋˜๋Š” ์…€์˜ ์†์„ฑ์„ ์ดˆ๊ธฐํ™” ํ•ด์ฃผ์—ˆ๋‹ค.

(์˜คํ”ˆ API๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ฒŒ ๋˜๋ฉด, ์ธํ„ฐ๋„ท ์ƒํ™ฉ์ด ์ข‹์ง€ ์•Š์€ ๊ฒฝ์šฐ์— ๋น ๋ฅด๊ฒŒ ์Šคํฌ๋กค์„ ํ–ˆ์„ ๋•Œ,
ํ•ด๋‹น ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์–ด์•ผํ•˜๋Š” ๊ณณ์ด ์•„๋‹Œ ๋‹ค๋ฅธ ์…€์—์„œ ๋ณด์—ฌ์ง€๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

์ด๋Ÿฐ ํ˜„์ƒ์€ ์…€์ด ์žฌ์‚ฌ์šฉ์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฏธ์ง€์™€ ํ…์ŠคํŠธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์œ„์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค!!

์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.๐Ÿ˜†)


< VC >



video ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์„ ๋ฆฌ์ŠคํŠธ ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•ด์คฌ๋‹ค.

var videoInfoList: [VideoInfo] = []


๊ทธ๋ฆฌ๊ณ  ์•ž์„œ ์Šคํ† ๋ฆฌ๋ณด๋“œ๋กœ ๋งŒ๋“ค์—ˆ๋˜ ํ…Œ์ด๋ธ” ๋ทฐ์— ๋Œ€ํ•œ ์„ค์ •๋ถ€ํ„ฐ ํ•ด์ฃผ์—ˆ๋‹ค.

extension ViewController:  UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return videoInfoList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: VideoTableViewCell.identifier, for: indexPath) as? VideoTableViewCell else { return UITableViewCell() }
        
        let product = self.videoInfoList[indexPath.row]
        cell.setThumbnail(imageURL: product.thumbnailUrl)
        cell.setTitle(title: product.title)
        return cell
    }
}


๊ทธ๋ฆฌ๊ณ  viewDidLoad์—์„œ

        videoListTableView.dataSource = self
        videoListTableView.delegate = self

ํ•ด์ฃผ๋ฉด ๋œ๋‹คใ…Žใ…Ž


+


์ถ”๊ฐ€์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋Š๋ฆฌ๊ฒŒ ๋„˜์–ด์˜ค๋Š” ๊ฒฝ์šฐ์—
์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณด์—ฌ์งˆ ์ˆ˜ ์žˆ๋„๋ก
Kingfisher ํŒจํ‚ค์ง€๋ฅผ ํ™œ์šฉํ•ด์„œ ์ถ”๊ฐ€ํ•ด์คฌ๋‹ค.

        let placeholderImage = UIImage(systemName: "video.fill")
        cell.thumbnailImage.kf.setImage(with: product.thumbnailUrl, placeholder: placeholderImage)
        cell.thumbnailImage.kf.indicatorType = .activity
        cell.thumbnailImage.kf.setImage(with: product.thumbnailUrl,
                                        options: [.transition(.fade(0.5)), .forceTransition, .keepCurrentImageWhileLoading])




๋‹ค์Œ์€ ๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ค๊ธฐ!!
(URLSession ํ™œ์šฉ)

let urlString = "https://gist.githubusercontent.com/poudyalanil/ca84582cbeb4fc123a13290a586da925/raw/14a27bd0bcd0cd323b35ad79cf3b493dddf6216b/videos.json"
    func getData() {
        guard let url = URL(string: urlString) else { return }
        let urlRequest = URLRequest(url: url)
        URLSession.shared.dataTask(with: urlRequest) { data, request, error in
            guard let data = data else { return }
            
            do {
                let decorder = JSONDecoder()
                let result = try decorder.decode([VideoInfo].self, from: data)
                self.videoInfoList = result
            } catch {
                print("error:\(error)")
            }
        }
        .resume()
    }


ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ ์—ฌ๊ธฐ์„œ ๋“ฑ์žฅํ•˜๋Š” ๋ฐ”๋ณด์ฐ..
guard let data = data else { return }
์—์„œ print๋กœ ๋ฐ์ดํ„ฐ ์ž˜ ๋„˜์–ด์™”๋Š”์ง€ ํ™•์ธํ•˜๋Š”๋ฐ..
์ž˜ ์ฐํžˆ๊ธธ๋ž˜ '์˜ค.. ์ž˜ ๋„˜์–ด์˜ค๋Š”๊ตฐ..'

๊ทธ๋Ÿฌ๊ณ  ์•„๋ฌด๋ฆฌ ์‹คํ–‰ํ•ด๋„ ์•ˆ๋œจ๊ธธ๋ž˜ ๋ฉ˜๋ถ•๐Ÿคฏ
์ฝ”๋“œ๋ฅผ ์ด๋ฆฌ์ €๋ฆฌ ๋ฐ”๊ฟ”๋ณด๊ณ  '๋‚ด๊ฐ€ ์ž˜๋ชป ์•Œ๊ณ ์žˆ๋‚˜..? ์ด๊ฒŒ ์•„๋‹Œ๊ฐ€..?'
ํ˜ผ์ž ๋‚œ๋ฆฌ๋‚œ๋ฆฌ ํ•˜๋‹ค๊ฐ€

ํŠœํ„ฐ๋‹˜ ์ฐพ์•„๊ฐ€์„œ ์ด์•ผ๊ธฐํ•˜๋‹ค๊ฐ€ ๋ฐœ๊ฒฌ๋œ..ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹

guard let data = data else { return }
๋‹ค์Œ์— error ์žˆ์œผ๋ฉด ํ™•์ธํ•ด์•ผ์ง€~ ํ•˜๊ณ  ์ผ๋˜
guard let error = error else { return }
์ด๋…€์„์„ ์ง€์šฐ์ง€ ์•Š์•„์„œ ์ƒ๊ธด ๋ฌธ์ œ...ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹....

data๊ฐ€ ์ž˜ ๋„˜์–ด์™”์œผ๋‹ˆ, error๋Š” ์—†์„ํ…๋ฐ
error๋ฅผ guard๋ฌธ์œผ๋กœ ๋‚จ๊ฒจ๋†”์„œ ์•„๋ž˜ do catch ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์•˜๋˜...๐Ÿ˜‚

๋‚ด ๋ˆˆ์—๋งŒ ๋ณด์ด์ง€ ์•Š๋Š”.. ์ฐพ์•„๊ฐ€๋ฉด ๋ฐœ๊ฒฌ๋˜๋Š”..MAGIC..๐Ÿช„



์•„๋ฌดํŠผ!! ์ด๋ ‡๊ฒŒ ํ•˜๊ณ !
tableView๋ฅผ reloadData๋ฅผ ํ•ด์ค˜์•ผํ•˜๋‹ˆ

do๋ฌธ์—์„œ 

DispatchQueue.main.async {
	self.videoInfoList.reloadData() 
}


ํ•ด์ฃผ๊ฑฐ๋‚˜!!

var videoInfoList: [VideoInfo] = [] {
        didSet {
            DispatchQueue.main.async {
                self.videoListTableView.reloadData()
            }
        }
    }


์•ž์„œ ๋งŒ๋“ค์—ˆ๋˜ videoInfoList์—์„œ didSet์œผ๋กœ reloadData ํ•ด์ฃผ๊ธฐ!



์ด์ œ ๊ฑฐ์˜ ๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„!!
AVPlayerContoller ๋„์šฐ๊ธฐ!

    func presentAVPlayerViewController(videoURL: URL) {
        let playerController = AVPlayerViewController()
        let player = AVPlayer(url: videoURL as URL)
        
        playerController.player = player
        
        self.present(playerController, animated: true) {
            player.play()
        }
    }

์ด๋ ‡๊ฒŒ ํ•ด์ฃผ๊ธฐ!
(import AVKit ํ•ด์ค˜์•ผํ•จ)


๋งˆ์ง€๋ง‰์œผ๋กœ ํ…Œ์ด๋ธ” ๋ทฐ ์…€์ด ์„ ํƒ๋์„๋•Œ์˜ ์ฝ”๋“œ๋งŒ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋!!

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let product = self.videoInfoList[indexPath.row]
        presentAVPlayerViewController(videoURL: product.videoUrl)
    }

 



์‹คํ–‰ํ•ด๋ณด๋ฉด!

0

 

์ง !โœจ


(์ „์ฒด ์ฝ”๋“œ๋Š” ์œ„์— github์— ์˜ฌ๋ ค๋’€์Šต๋‹ˆ๋‹ค!)


 

728x90