Notice
Recent Posts
Recent Comments
Link
ยซ   2024/05   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
๊ด€๋ฆฌ ๋ฉ”๋‰ด

lgvv98

part5 (ch6). KeywordNews ์ฝ”๋“œ๋ฆฌ๋ทฐ ๋ณธ๋ฌธ

โš ๏ธ deprecated โš ๏ธ/ํŒจ์บ (์ดˆ๊ฒฉ์ฐจ)

part5 (ch6). KeywordNews ์ฝ”๋“œ๋ฆฌ๋ทฐ

๐Ÿฅ• ์บ๋Ÿฟ๋งจ 2022. 2. 15. 03:58

โœ… ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ•ด๋ณด์ž. 

 

๊ทธ๋ƒฅ ๋‹ค๋ฅธ ์‚ฌ๋žŒ ์ข‹์€ ์ฝ”๋“œ๋ฅผ ๋ณด๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ์‹ค๋ ฅ ๋งŽ์ด ๋Š๋Š”๊ฑฐ ๊ฐ™์•„ ใ…Žใ……ใ…Ž

๋‚˜์ค‘์— MVVM๊ณผ Rx๋กœ ๋ฐ”๊พธ๋Š” ๋ฐฉ๋ฒ•๋„ ๊ณ ๋ฏผํ•ด๋ณด์ž.

ํŒŒ์ผ ๊ตฌ์กฐ๋„

 

โœ… String+

//
//  String+.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/08.
//

import Foundation

extension String {
    // html์„ string์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์ฝ”๋“œ
    var htmlToString: String {
        guard let data = self.data(using: .utf8) else { return "" }
        
        do {
            return try NSAttributedString(
                data: data,
                options: [
                    .documentType: NSAttributedString.DocumentType.html,
                    .characterEncoding: String.Encoding.utf8.rawValue
                
                ],
                documentAttributes: nil
            ).string
        } catch {
            return ""
        }
    }
}

Utils์—์„œ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋˜๋ฐ, ๊ทผ๋ฐ ์ด๋ฒˆ์— ์ง„์งœ ๋งŽ์ด ๋ฐฐ์› ๋‹ค๊ณ  ๋Š๋ผ๋Š”๊ฒŒ, ์˜ˆ์ „์— API๋กœ ๋‚ด๋ ค๋ฐ›์•˜๋Š”๋ฐ html tag๊ฐ€ ์ง€์›Œ์ง€์ง€๊ฐ€ ์•Š๊ณ  ๋ณด์—ฌ์ง€๊ธธ๋ž˜ ์ด๋Ÿฐ๊ฒŒ ์žˆ๋Š”์ง€ ๋ชจ๋ฅด๊ณ  text๋ฅผ ๊ฒ€์‚ฌํ•˜๋ฉด์„œ ํ•˜๋‚˜ํ•˜๋‚˜.. ๋‚ด๊ฐ€ ์ง์ ‘ ์กฐ๊ฑด์— ๋งž๊ฒŒ ์—†์• ๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๋งŒ๋“ค์—ˆ์—ˆ๋‹ค.

๊ทผ๋ฐ, ์ด๊ฒŒ ์ด๋Ÿฐ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๊ณ ..?

 

ํ•˜.. ์ •๋ง ์ด๋ ‡๊ฒŒ ๋˜ ํ•˜๋‚˜ ๋ฐฐ์›Œ๊ฐˆ ์ˆ˜ ์žˆ์–ด์„œ ๊ฐ์‚ฌํ•˜๋‹ค ๐Ÿ™ 

 

โœ… Model ํด๋”

//
//  NewRequestModel.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/07.
//

import Foundation

struct NewsRequestModel: Codable {
    /// ์‹œ์ž‘ ์ธ๋ฑ์Šค. 1 ~ 1000
    let start: Int
    /// ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ๊ฑด์ˆ˜, 10 ~ 1000
    let display: Int
    /// ๊ฒ€์ƒ‰์–ด
    let query: String
}

โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ

//
//  NewsResponseModel.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/07.
//

import Foundation

struct NewsResponseModel: Codable {
    var items: [News] = []
}

struct News: Codable {
    let title: String
    let link: String
    let description: String
    let pubDate: String
}

 

โœ…

//
//  NewsSearchManager.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/07.
//

import Alamofire
import Foundation

protocol NewsSearchManagerProtocol {
    func request(
        from keyword: String,
        start: Int,
        display: Int,
        completionHandler: @escaping ([News]) -> Void
    )
}

struct NewsSearchManager: NewsSearchManagerProtocol {
    func request(
        from keyword: String,
        start: Int,
        display: Int,
        completionHandler: @escaping ([News]) -> Void
    ) {
        guard let url = URL(string: "https://openapi.naver.com/v1/search/news.json") else { return }

        let parameters = NewsRequestModel(start: start, display: display, query: keyword)
        let headers: HTTPHeaders = [
            "X-Naver-Client-id": "{Your Api Key}",
            "X-Naver-Client-Secret": "{Your Api Key}"
        ]

        AF
            .request(url, method: .get, parameters: parameters, headers: headers)
            .responseDecodable(of: NewsResponseModel.self) { response in
                switch response.result {
                case .success(let result):
                    completionHandler(result.items)
                case .failure(let error):
                    print(error)
                }
            }
            .resume()
    }
}

AF๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋ฐ”๋กœ ๋””์ฝ”๋”ฉํ•˜๋Š”๊ฒŒ ์ •๋ง ์ธ์ƒ์ ์ด๋‹ค ๋ฉ”๋ชจ..ํ•˜๊ณ  ๊ธฐ์–ต..!

 

โœ… NewsListViewController

//
//  NewsListViewController.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/05.
//

import UIKit
import SnapKit
import Then

class NewsListViewController: UIViewController {

    private lazy var presenter = NewsListPresenter(viewController: self)
    
    private lazy var refreshControl = UIRefreshControl().then {
        $0.addTarget(self, action: #selector(didCalledRefresh), for: .valueChanged)
    }
    
    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        
        tableView.delegate = presenter
        tableView.dataSource = presenter
        
        tableView.register(
            NewsListTableViewCell.self,
            forCellReuseIdentifier: NewsListTableViewCell.identifier
        )

        // ํ—ค๋”๋“ฑ๋ก ์ฝ”๋“œ๊ฐ€ ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค.
        tableView.register(
            NewsListTableViewHeaderView.self,
            forHeaderFooterViewReuseIdentifier: NewsListTableViewHeaderView.identifier
        )
        
        tableView.refreshControl = refreshControl
        
        return tableView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.viewDidLoad()
        
        NewsSearchManager().request(from: "์•„์ดํฐ", start: 1, display: 20) { newsArray in
            print(newsArray)
        }
    }


}
extension NewsListViewController: NewsListProtocol {
    func setupNavigationBar() {
        navigationItem.title = "NEWS"
        navigationController?.navigationBar.prefersLargeTitles = true
    }
    
    func setupLayout() {
        view.addSubview(tableView)
        
        tableView.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
    }
    
    func endRefreshing() {
        refreshControl.endRefreshing()
    }
    
    func moveToNewsWebViewController(with news: News) {
        let newWebController = NewWebViewContrller(news: news)
        navigationController?.pushViewController(newWebController, animated: true)
    }
    
    func reloadTableView() {
        tableView.reloadData()
    }
}


private extension NewsListViewController {
    @objc func didCalledRefresh() {
        presenter.didCalledRefresh()
    }
}

 

โœ… ์—ฌ๊ธฐ๋„ ์›Œ๋‚™ ๋ฐฐ์šธ๊ฒŒ ๋งŽ์•„์„œ..! 

//
//  NewsListPresenter.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/05.
//

import Foundation
import UIKit

protocol NewsListProtocol: AnyObject {
    func setupNavigationBar()
    func setupLayout()
    func endRefreshing()
    func moveToNewsWebViewController(with news: News)
    func reloadTableView()
}

final class NewsListPresenter: NSObject {
    private weak var viewController: NewsListProtocol?
    private let newsSearchManager: NewsSearchManagerProtocol
    
    private var newsList: [News] = []
    private var currentKeyword = ""
    /// ์ง€๊ธˆ๊นŒ์ง€  request๋œ, ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์–ด๋””์ธ์ง€
    private var currentPage: Int = 0
    /// ํ•œ ํŽ˜์ด์ง€์— ์ตœ๋Œ€ ๋ช‡ ๊ฐœ๊นŒ์ง€ ๋ณด์—ฌ์ค„ ๊ฒƒ์ธ์ง€
    private let display: Int = 20
    
    private let tags: [String] = ["IT", "์•„์ดํฐ", "๊ฐœ๋ฐœ", "๊ฐœ๋ฐœ์ž", "ํŒ๊ต", "๊ฒŒ์ž„", "์•ฑ๊ฐœ๋ฐœ", "๊ฐ•๋‚จ", "์Šคํƒ€ํŠธ์—…"]
    
    init(
        viewController: NewsListProtocol,
        newsSearchManager: NewsSearchManagerProtocol = NewsSearchManager()
    ) {
        self.viewController = viewController
        self.newsSearchManager = newsSearchManager
    }
    
    func viewDidLoad() {
        viewController?.setupNavigationBar()
        viewController?.setupLayout()
    }
    
    func didCalledRefresh() {
        requestNewsList(isNeededToReset: true)

    }
}

extension NewsListPresenter: NewsListTableViewHeaderViewDelegate {
    func didSelectTag(_ selectedIndex: Int) {
        currentKeyword = tags[selectedIndex]
        requestNewsList(isNeededToReset: true)
    }
    
    
}

extension NewsListPresenter: UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let news = newsList[indexPath.row]
        viewController?.moveToNewsWebViewController(with: news)
    }

    // ์•ˆ๋ณด์ด๋Š” ์…€์ด ๋ณด์—ฌ์ง€๋ ค๊ณ  ํ•˜๋ฉด ์ด ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค. -> ํŽ˜์ด์ง€ ๋„ค์ด์…˜์„ ์œ„ํ•ด (๋ฌดํ•œ ์Šคํฌ๋กค)
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

        let currentRow = indexPath.row // ํ˜„์žฌ ๋ณด์—ฌ์ง€๊ณ  ์žˆ๋Š” Row

        // ๋์— ๋‹ฟ๊ธฐ 3๊ฐœ ์ „์— ์ด ์ž‘์—… ์ž‘์—… ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ guard ๋ฌธ 
        guard (currentRow % 20) == display - 3 && (currentRow / display) == (currentPage - 1) else { return } // ๋„ค์ด๋ฒ„์˜ currentPage๊ฐ€ 1๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ

        requestNewsList(isNeededToReset: false)
    }
    
}

extension NewsListPresenter: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        newsList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        guard let cell = tableView.dequeueReusableCell(
            withIdentifier: NewsListTableViewCell.identifier,
            for: indexPath
        ) as? NewsListTableViewCell else { return UITableViewCell() }
        
        let news = newsList[indexPath.row]
        cell.setup(news: news)
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = tableView.dequeueReusableHeaderFooterView(
            withIdentifier: NewsListTableViewHeaderView.identifier
        ) as? NewsListTableViewHeaderView
            
        header?.setup(tags: tags, delegate: self)
        
        return header
    }
}

private extension NewsListPresenter {
    // ํŽ˜์ด์ง€ ๋„ค์ด์…˜์„ ํ†ตํ•˜์—ฌ ๊ตฌํ˜„๋˜์—ˆ์Œ.
    func requestNewsList(isNeededToReset: Bool) {
        // reset์„ ํ•˜๋Š” ์ด์œ ๋Š” ์Šคํฌ๋กค์„ ๋ฌดํ•œํžˆ ๋‚ด๋ ธ๋‹ค๊ฐ€ ์œ„์—์„œ refreshControl์„ ํ•  ๊ฒฝ์šฐ ์Šคํฌ๋กค ๋ฐ”ํ€ด๊ฐ€ ๋‹ค์‹œ ์ปค์ง€๊ฒŒ ๋” ์กฐ์ •ํ•˜๊ธฐ ์œ„ํ•จ. ์ฆ‰ ์œ„์—์„œ ๋ฆฌํ”„๋ ˆ์‹œ๋ฅผ ์‹ค์‹œํ•  ๊ฒฝ์šฐ ์…€์ด 20๊ฐœ๋กœ ๋งž์ถฐ์ง„๋‹ค.
        if isNeededToReset {
            currentPage = 0
            newsList = []
        }
        
        newsSearchManager.request(
            from: currentKeyword,
            start: (currentPage * display) + 1, // ๋„ค์ด๋ฒ„ API๋Š” ์‹œ์ž‘์ ์ด 1๋ถ€ํ„ฐ๋ผ์„œ
            display: 20) { [weak self] newValue in
                self?.newsList += newValue // ํŽ˜์ด์ง€ ๋„ค์ด์…˜์„ ์œ„ํ•œ ์ฝ”๋“œ(๋Œ€์ž…์ด ์•„๋‹ˆ๋ผ ๋”ํ•˜๊ธฐ)
                self?.currentPage += 1
                self?.viewController?.reloadTableView()
                self?.viewController?.endRefreshing()
            }
    }
}

๋ฌดํ•œ ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•˜๋Š” ์ฝ”๋“œ์ธ๋ฐ ๋…ํŠนํ•ด!

 

 

โœ… NewsListTableViewCell

//
//  NewsListTableViewCell.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/05.
//

import Foundation
import SnapKit
import Then
import UIKit
import TTGTags

final class NewsListTableViewCell: UITableViewCell {
    static let identifier = "NewsListTableViewCell"
    
    private lazy var titleLabel = UILabel().then {
        $0.font = .systemFont(ofSize: 15.0, weight: .semibold)
    }
    
    private lazy var descriptionLabel  = UILabel().then {
        $0.font = .systemFont(ofSize: 14.0, weight: .medium)
    }
    
    private lazy var dateLabel = UILabel().then {
        $0.font = .systemFont(ofSize: 12.0, weight: .medium)
        $0.textColor = .secondaryLabel
    }
    
    func setup(news: News) {
        setupLayout()
        
        accessoryType = .disclosureIndicator
        selectionStyle = .none
        
        titleLabel.text = news.title.htmlToString
        descriptionLabel.text = news.description.htmlToString
        dateLabel.text = news.pubDate.htmlToString
    }
}

private extension NewsListTableViewCell {
    // ์…€ ํฌ๊ธฐ๋ฅผ height์œผ๋กœ ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ์ž๋™์œผ๋กœ ์žกํž˜!
    func setupLayout() {
        [titleLabel, descriptionLabel, dateLabel]
            .forEach { addSubview($0) }

        let superViewInset: CGFloat = 16.0

        titleLabel.snp.makeConstraints {
            $0.leading.equalToSuperview().inset(superViewInset)
            $0.trailing.equalToSuperview().inset(48.0)
            $0.top.equalToSuperview().inset(superViewInset)
        }

        let verticalSpacing: CGFloat = 4.0

        descriptionLabel.snp.makeConstraints{
            $0.leading.equalTo(titleLabel.snp.leading)
            $0.trailing.equalTo(titleLabel.snp.trailing)
            $0.top.equalTo(titleLabel.snp.bottom).offset(verticalSpacing)
        }

        dateLabel.snp.makeConstraints {
            $0.leading.equalTo(titleLabel.snp.leading)
            $0.trailing.equalTo(titleLabel.snp.trailing)
            $0.top.equalTo(descriptionLabel.snp.bottom).offset(verticalSpacing)
            $0.bottom.equalToSuperview().inset(superViewInset)
        }
    }
}

 

 

โœ… NewsListTableViewHeaderView

//
//  NewsListTableViewHeaderView.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/05.
//

import UIKit

import SnapKit
import TTGTags

protocol NewsListTableViewHeaderViewDelegate: AnyObject {
    func didSelectTag(_ selectedIndex: Int)
}

final class NewsListTableViewHeaderView: UITableViewHeaderFooterView {
    static let identifier = "NewsListTableViewHeaderView"

    private lazy var tagCollectionView = TTGTextTagCollectionView()
    
    private weak var delegate: NewsListTableViewHeaderViewDelegate?
    
    private var tags: [String] = []

    
    func setup(
        tags: [String],
        delegate: NewsListTableViewHeaderViewDelegate
    ) {
        self.tags = tags
        self.delegate = delegate
        
        contentView.backgroundColor = .systemBackground
        
        setupTagCollectionViewLayout()
        setupTagCollectionView()
    }
}


extension NewsListTableViewHeaderView: TTGTextTagCollectionViewDelegate {
    func textTagCollectionView(_ textTagCollectionView: TTGTextTagCollectionView!, didTap tag: TTGTextTag!, at index: UInt) {
        print("textTagCollectionView \(tag)")
        guard tag.selected else { return } // ํƒœ๊ทธ๊ฐ€ ์…€๋ ‰ ๋˜์—ˆ์„ ๋•Œ๋งŒ ๋ถˆ๋ ค์ง€๊ฒŒ๋” ํ•„ํ„ฐ๋ง ํ•ด์ฃผ๊ธฐ -> ํƒœ๊ทธ๊ฐ€ ์…€๋ ‰๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ์กฐ๊ฑด์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋„ ๋ถˆ๋ ค์ง€๊ธฐ ๋•Œ๋ฌธ์—
        
        delegate?.didSelectTag(Int(index))
    }
    
    func textTagCollectionView(_ textTagCollectionView: TTGTextTagCollectionView!, canTap tag: TTGTextTag!, at index: UInt) -> Bool {
        print("canTap")
        
        return true
    }
    
}

private extension NewsListTableViewHeaderView {
    func setupTagCollectionViewLayout() {
        addSubview(tagCollectionView)
        
        tagCollectionView.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
    }
    
    func setupTagCollectionView() {
        tagCollectionView.delegate = self
        tagCollectionView.numberOfLines = 1
        tagCollectionView.scrollDirection = .horizontal
        tagCollectionView.showsVerticalScrollIndicator = false
        tagCollectionView.selectionLimit = 1
        
        let insetValue: CGFloat = 16.0
        tagCollectionView.contentInset = UIEdgeInsets(
            top: insetValue,
            left: insetValue,
            bottom: insetValue,
            right: insetValue
        )
        
        let cornerRadiusValue: CGFloat = 12.0
        let shadowOpacity: CGFloat = 0.0
        let extraSpace = CGSize(width: 20.0, height: 12.0)
        let color = UIColor.systemOrange
        
        let style = TTGTextTagStyle()
        style.backgroundColor = color
        style.cornerRadius = cornerRadiusValue
        style.borderWidth = 0.0
        style.shadowOpacity = shadowOpacity
        style.extraSpace = extraSpace

        let selectedStyle = TTGTextTagStyle()
        selectedStyle.backgroundColor = .white
        selectedStyle.cornerRadius = cornerRadiusValue
        selectedStyle.shadowOpacity = shadowOpacity
        selectedStyle.extraSpace = extraSpace
        selectedStyle.borderColor = color
        
        tags.forEach { tag in
            let font = UIFont.systemFont(ofSize: 14.0, weight: .semibold)
            let tagContents = TTGTextTagStringContent(
                text: tag,
                textFont: font,
                textColor: .white
            )
            let selectedTagContents = TTGTextTagStringContent(
                text: tag,
                textFont: font,
                textColor: color
            )

            let tag = TTGTextTag(
                content: tagContents,
                style: style,
                selectedContent: selectedTagContents,
                selectedStyle: selectedStyle
            )

            tagCollectionView.addTag(tag)
        }
    }
}

์ด๊ฑด ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ์ด๋‹ค.

๋”ฐ๋กœ ํฌ์ŠคํŒ…์„ ํ•˜์ž!

 

 

โœ… NewWebViewContrller

//
//  NewWebView.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/07.
//

import Foundation
import WebKit
import UIKit

import SnapKit

final class NewWebViewContrller: UIViewController {
    private let news: News
    private let webView = WKWebView()
    
    private var rightBarButtonItem = UIBarButtonItem(
        image: UIImage(systemName: "link"),
        style: .plain,
        target: self,
        action: #selector(didTapRightBarButtonItem))
    
    init(news: News) {
        self.news = news
        
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        setupNavigationbBar()
        setupWebView()
    }
}

private extension NewWebViewContrller {
    func setupNavigationbBar() {
        navigationItem.title = news.title.htmlToString
        navigationItem.rightBarButtonItem = rightBarButtonItem
    }
    
    func setupWebView() {
        guard let linkURL = URL(string: news.link) else {
            // ๋งํฌ๊ฐ€ ์—†๋‹ค๋ฉด ์ž๋™์œผ๋กœ ํŒํ•ด์ฃผ๋ฉด UX์ ์œผ๋กœ ๋” ์ข‹์€๋“ฏ
            navigationController?.popViewController(animated: true)
            return
        }
        
        view = webView
        
        let urlRequest = URLRequest(url: linkURL)
        webView.load(urlRequest)
    }
    
    @objc func didTapRightBarButtonItem() {
        UIPasteboard.general.string = news.link // ํด๋ฆฝ ๋ณด๋“œ์— ๋ณต์‚ฌํ•˜๋Š” ๋ฉ”์†Œ๋“œ
    }
}

 

 

โœ… SceneDelegate

//
//  SceneDelegate.swift
//  KeywordNews
//
//  Created by Hamlit Jason on 2022/02/05.
//

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    var window: UIWindow?
    
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        window = UIWindow(windowScene: windowScene)
        window?.backgroundColor = .systemBackground
        window?.tintColor = .systemOrange // ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๊ฐ™์€ ์ปฌ๋Ÿฌ๋ฅผ ํ•œ๋ฒˆ์— ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.
        window?.rootViewController = UINavigationController(rootViewController: NewsListViewController())
        window?.makeKeyAndVisible()
    }
}

 

 

ํ•ด์•ผํ•  ์ผ - ์ฝ”๋“œ ๋ฆฌ๋ทฐ ํ•˜๋‚˜ํ•˜๋‚˜ ๋ถ„์„

ํ…Œ์Šคํฌ ์ฝ”๋“œ๋„ ๋”ฐ๋กœ ํฌ์ŠคํŒ…

๊นƒํ—ˆ๋ธŒ์— ์˜ฌ๋ฆด๋•Œ API ํ‚ค ์‚ญ์ œํ•˜๊ธฐ

Comments