apple/iOS, UIKit, Documentation

iOS 오픈소스 라이선스 페이지 (뱅크샐러드)

lgvv 2022. 2. 23. 22:56

iOS 오픈소스 라이선스 페이지 (뱅크샐러드)



오픈소스 라이선스 페이지를 만들어보고자 함.

뱅크샐러드에서 오픈소스에 채용 이스터에그를 넣어두었는데 사이드 프로젝트에서도 도입하고 했고, 인턴하는 회사에서도 넣자고 해서 겸사겸사 만들어보고자 함.



코드 파일

아래는 샘플 파일

OpenSourceTableView.zip
0.08MB

 

 

 

 

뱅크샐러드 오픈소스 페이지

 

✅ 환경

  •  Swift 5
  •  Xcode 13
  •  RxSwift 6
  •  SnapKit
  •  Then

 

폴더 구조

폴더의 구조


간단한 페이지라 MVC 기반으로 빠르게 작업하고자 함.

 

 

🟠 UIViewController+.swift

import Foundation
import UIKit
import Then

extension UIViewController {
    func showOpenSourceWebVC(data: OpenSourceModel) {
        let vc = OpenSourceWebVC(data: data)
        navigationController?.pushViewController(vc, animated: true)
    }
    
    func setContentView() -> UIViewController {
        let contentVC = UIViewController()
        
        let customView = UIImageView().then {
            $0.image = UIImage(named: "ic_swiftUI_icon")
        }
        
        contentVC.view = customView
        contentVC.preferredContentSize.height = 200
        
        return contentVC
    }
}

여기가 나중에 actionSheet에서 이미지 나타나는 부분 세팅도 처리하는 부분

 

🟠 UIActivityIndicatorView+Rx.swift

import Foundation
import RxSwift
import RxCocoa

import UIKit
import RxSwift

extension Reactive where Base: UIActivityIndicatorView {
    /// Bindable sink for `startAnimating()`, `stopAnimating()` methods.
    public var isAnimating: Binder<Bool> {
        Binder(self.base) { activityIndicator, active in
            if active {
                activityIndicator.startAnimating()
            } else {
                activityIndicator.stopAnimating()
            }
        }
    }
}

UIActivityIndicatorView를 rx로 사용하려고 만든 부분

 

🟠 OpenSourceView.swift

import Foundation
import UIKit

import SnapKit
import Then
import RxSwift
import RxCocoa


final class OpenSourceView: UIView {
    lazy var tableView = UITableView().then {
        $0.register(OpenSourceCell.self, forCellReuseIdentifier: OpenSourceCell.identifier)
        $0.backgroundColor = .yellow
    }
    
    func commonInit() {
        setupViews()
    }
}

extension OpenSourceView {
    
    func setupViews() {
        [tableView].forEach { addSubview($0) }
        
        tableView.snp.makeConstraints {
            $0.edges.equalTo(safeAreaLayoutGuide)
        }
    }
}

 

🟠 OpenSourceVC.swift

import UIKit
import SafariServices

import RxSwift
import Then
import SnapKit

class OpenSourceVC: UIViewController {
    private var openSourceView: OpenSourceView { view as! OpenSourceView }
    private var openSourceVM = OpenSourceVM()
    
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        bind()
    }
    
    override func loadView() {
        view = OpenSourceView()
        openSourceView.commonInit()
    }
}

private extension OpenSourceVC {
    private func bind() {
        openSourceVM.openSourceInfoDriver
            .asObservable()
            .bind(to: openSourceView.tableView.rx.items(
                cellIdentifier: OpenSourceCell.identifier,
                cellType: OpenSourceCell.self)
            ) {
                index, item, cell in
                cell.setup(data: item)
            }
            .disposed(by: disposeBag)
        
        openSourceView.tableView.rx.modelSelected(OpenSourceModel.self)
            .bind { [weak self] data in
                self?.showOpenSourceWebVC(data: data)
            }
            .disposed(by: disposeBag)
        
        openSourceView.tableView.rx.willDisplayCell
            .asObservable()
            .map{ $1.row }
            .map{ ($0, self.openSourceVM.openSourceInfoDatalength) }
            .debug("VC ->")
            .bind(to: openSourceVM.willDisplayCellSubject)
            .disposed(by: disposeBag)
        
        openSourceVM.willDisplayCellDriver
            .asObservable()
            .bind { [weak self] in
                self?.actionSheet(execute: $0)
            }
            .disposed(by: disposeBag)
    }
}

private extension OpenSourceVC {
    private func actionSheet(execute: Bool) {
        guard execute else { return }
        
        let title = "앗... 저희를 깨우셨네요"
        let message = """
                    안녕하세요. 대한민국 국가대표 경기가 다음주 수요일에 열립니다.
                    대한민국 국가대표를 위해 응원의 메시지를 전달하고 싶으시다면 010 0000 0000으로 연락주세요
                    대한민국 최고의 개발자는 역시 iOS 개발자!
                    이모지는 과연 어떻게 될까요? 🍎 <- 이건 빨간 사과
                    """
        let alert = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
        
        alert.setValue(setContentView(), forKey: "contentViewController")
        
        let okTitle = "🧑🏻‍💻이 블로그에 관심이 있으시다면?🧑🏻‍💻"
        let okAction = UIAlertAction(title: okTitle, style: .default) { _ in
            let request = "https://rldd.tistory.com"
            guard let url = URL(string: request) else { return }
            guard UIApplication.shared.canOpenURL(url) else { return }
            
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        }
        
        let cancelTitle = "나가기🥲"
        let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel)
        
        [okAction, cancelAction].forEach{ alert.addAction($0) }
        
        self.present(alert, animated: true)
    }
}

 

🟠 OpenSourceVM.swift

import Foundation

import RxSwift
import RxCocoa
import UIKit

class OpenSourceVM {
    var openSourceInfoDatalength = OpenSourceInfo.data.count - 1
    private var openSourceInfoSubject = BehaviorSubject<[OpenSourceModel]>(value: OpenSourceInfo.data)
    
    typealias currentRow = Int
    typealias maxRow = Int
    // Inputs
    var willDisplayCellSubject = PublishSubject<(Int, Int)>()
    
    // Outputs
    var openSourceInfoDriver: Driver<[OpenSourceModel]>
    var willDisplayCellDriver: Driver<Bool>
    
    init() {
        openSourceInfoDriver = self.openSourceInfoSubject
            .asObservable()
            .asDriver(onErrorJustReturn: [])
        
        willDisplayCellDriver = self.willDisplayCellSubject
            .asObservable()
            .filter{ $0 == $1 }
            .take(1)
            .map{ _,_ in true }
            .asDriver(onErrorJustReturn: false)
    }
}

 

🟠 OpenSourceModel.swift

import Foundation
import RxSwift

struct OpenSourceModel {
    var link: String
    var name: String {
        get {
            link.components(separatedBy: "/").last ?? ""
        }
        set { }
    }
   
    init(name: String, link: String){
        self.link = link
        self.name = name
    }
    
    init(link: String) {
        self.link = link
        
    }
}

final class OpenSourceInfo {
    
    static let data: [OpenSourceModel] = [
        OpenSourceModel(link: "https://github.com/ReactiveX/A_없는_링크테스트"),
        OpenSourceModel(link: "https://github.com/ReactiveX/RxSwift"),
        OpenSourceModel(link: "https://github.com/RxSwiftCommunity/RxDataSources"),
        OpenSourceModel(link: "https://github.com/RxSwiftCommunity/RxGesture"),
        OpenSourceModel(link: "https://github.com/Alamofire/Alamofire"),
        OpenSourceModel(link: "https://github.com/JanGorman/Agrume"),
        OpenSourceModel(link: "https://github.com/SnapKit/SnapKit"),
        OpenSourceModel(link: "https://github.com/krzyzanowskim/CryptoSwift"),
        OpenSourceModel(link: "https://github.com/SwiftyJSON/SwiftyJSON"),
        OpenSourceModel(link: "https://github.com/realm/realm-swift"),
        OpenSourceModel(link: "https://github.com/onevcat/Kingfisher"),
        OpenSourceModel(link: "https://github.com/daltoniam/Starscream"),
        OpenSourceModel(link: "https://github.com/airbnb/lottie-ios"),
        OpenSourceModel(link: "https://github.com/devxoul/Then"),
        OpenSourceModel(link: "https://github.com/krzysztofzablocki/Sourcery"),
        OpenSourceModel(link: "https://github.com/BranchMetrics/ios-branch-deep-linking-attribution"),
        OpenSourceModel(link: "https://github.com/ReactiveX/RxSwift/tree/main/RxBlocking"),
        OpenSourceModel(link: "https://github.com/ReactiveX/RxSwift/tree/main/RxTest"),
        OpenSourceModel(link: "https://github.com/stanfy/SwiftyMock"),
    ].sorted {
        $0.link.components(separatedBy: "/").last ?? "" < $1.link.components(separatedBy: "/").last ?? ""
    }
}

 

🟠 OpenSourceCell.swift

import Foundation
import UIKit

import SnapKit
import Then

class OpenSourceCell: UITableViewCell {
    static let identifier = "OpenSourceCell"
    
    private lazy var name = UILabel().then {
        $0.font = .systemFont(ofSize: 15.0, weight: .semibold)
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        accessoryType = .disclosureIndicator
        selectionStyle = .none
        
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension OpenSourceCell {
    func setup(data: OpenSourceModel) {
        name.text = data.name
    }
    
    func setupViews() {
        [name].forEach { addSubview($0) }
        
        let superViewInset: CGFloat = 16.0
        
        name.snp.makeConstraints {
            $0.edges.equalToSuperview().inset(superViewInset)
        }
    }
}

 

🟠 OpenSourceWebVC.swift

import Foundation
import WebKit
import UIKit

import SnapKit
import RxSwift
import RxCocoa

class OpenSourceWebVC: UIViewController {
    private var openSourceWebView: OpenSourceWebView { view as! OpenSourceWebView }
    
    private let data: OpenSourceModel
    var disposedBag = DisposeBag()
    
    var activatedSubject = PublishSubject<Bool>()
    
    init(data: OpenSourceModel) {
        self.data = data
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        setupWebView()
        bind()
    }
    
    override func loadView() {
        view = OpenSourceWebView()
        openSourceWebView.commonInit()
    }
}

private extension OpenSourceWebVC {
    func setupWebView() {
        guard let linkURL = URL(string: data.link) else {
            navigationController?.popViewController(animated: true)
            return
        }
        
        let urlRequest = URLRequest(url: linkURL)
        openSourceWebView.webView.load(urlRequest)
    }
}

extension OpenSourceWebVC {
    func bind() {
        openSourceWebView.webView.rx.didCommit
            .map{ _ in false }
            .bind(to: activatedSubject)
            .disposed(by: disposedBag)
        
        openSourceWebView.webView.rx.didFinishLoad
            .map{ _ in true }
            .bind(to: activatedSubject)
            .disposed(by: disposedBag)
        
        openSourceWebView.webView.rx.didFailLoad
            .map{ _ in false }
            .bind(to: activatedSubject)
            .disposed(by: disposedBag)
        
        activatedSubject.asObservable()
            .observe(on: MainScheduler.asyncInstance)
            .map { !$0 }
            .debug("😅")
            .bind(to: openSourceWebView.activityIndicator.rx.isAnimating)
            .disposed(by: disposedBag)
    }
}

 

🟠 OpenSourceWebView.swift

import Foundation
import UIKit
import WebKit

import SnapKit
import Then
import RxSwift
import RxCocoa

class OpenSourceWebView: UIView {
    let webView = WKWebView()
    let activityIndicator = UIActivityIndicatorView().then {
        $0.startAnimating()
    }
    
    func commonInit() {
        backgroundColor = .white
        setupViews()
    }
}

extension OpenSourceWebView {
    
    func setupViews() {
        [webView, activityIndicator].forEach { addSubview($0) }
        
        webView.snp.makeConstraints {
            $0.edges.equalTo(safeAreaLayoutGuide)
        }
        activityIndicator.snp.makeConstraints {
            $0.center.equalTo(safeAreaLayoutGuide)
        }
    }
}

 

 

 

(참고)

https://github.com/devxoul/Carte