iOS 오픈소스 라이선스 페이지 (뱅크샐러드)
오픈소스 라이선스 페이지를 만들어보고자 함.
뱅크샐러드에서 오픈소스에 채용 이스터에그를 넣어두었는데 사이드 프로젝트에서도 도입하고 했고, 인턴하는 회사에서도 넣자고 해서 겸사겸사 만들어보고자 함.
코드 파일
아래는 샘플 파일
✅ 환경
- 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)
}
}
}
(참고)
'apple > iOS, UIKit, Documentation' 카테고리의 다른 글
iOS 최상단 ViewController + UIWindow (keywindow) (0) | 2022.03.15 |
---|---|
[iOS/Swift] init과 super.init에 대해서 알아보자. 🤔 (0) | 2022.02.25 |
iOS Lottie 알아보기 (.json, .lottie) (0) | 2022.02.19 |
Showing All Messages Undefined symbol: __swift_FORCE_LOAD_$_XCTestSwiftSupport (0) | 2022.02.18 |
[iOS] TTGTagCollectionView에 대해서 알아보자. (0) | 2022.02.17 |