티스토리 뷰
728x90
반응형
내용
- MVVM 패턴을 적용해보자
- 데이터바인딩 방법은 Observable 클래스 사용
QR코드 뷰와 뷰모델만 소개해보겠다.
View
import UIKit
import SnapKit
class QRCodeViewController: UIViewController {
// MARK: - Properties
// ✅ view model
let viewModel = QRCodeViewModel()
let closeButton = UIButton()
let switchShakeButton = UIButton()
let privateQuestionButton = UIButton()
let titleLabel = UILabel()
let subtitleLabel = UILabel()
let privatetextLabel = UILabel()
let privateNumberLabel = UILabel()
let timeLabel = UILabel()
let qrcodeBackView = UIView()
let qrcodeTopView = UIView()
let qrcodeImageBackView = UIView()
let privateStackView = UIStackView()
let qrcodeImageView = QRCodeView()
var isSpeechBubble = false
var isShakeAvailable = true
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configUI()
setLayout()
setBinding()
setNotification()
}
}
// MARK: - Extensions
extension QRCodeViewController {
// ✅ UI 의 속성 설정
private func configUI(){
view.backgroundColor = .white
titleLabel.text = "입장을 위한 QR X COOV"
titleLabel.font = UIFont.boldSystemFont(ofSize: 17)
subtitleLabel.text = "이용하려는 시설에 QR코드로 체크인하거나 수기명부에\n휴대전화번호 대신 개인안심번호를 기재하세요."
subtitleLabel.font = UIFont.systemFont(ofSize: 13)
subtitleLabel.textColor = .gray
subtitleLabel.numberOfLines = 2
subtitleLabel.textAlignment = .center
closeButton.setImage(UIImage(systemName: "xmark"), for: .normal)
closeButton.tintColor = .black
closeButton.setPreferredSymbolConfiguration(.init(pointSize: 20, weight: .regular), forImageIn: .normal)
closeButton.addAction(UIAction { _ in
self.viewModel.dismissToMainVC(self)
}, for: .touchUpInside)
switchShakeButton.tintColor = .gray
switchShakeButton.setTitleColor(.gray, for: .normal)
switchShakeButton.titleLabel?.font = UIFont.systemFont(ofSize: 13)
switchShakeButton.addAction(UIAction { _ in
self.viewModel.setShakeAvailable()
self.viewModel.setShakeText()
self.viewModel.setShakeImage()
print(self.viewModel.isShakeAvailable.value)
}, for: .touchUpInside)
qrcodeBackView.backgroundColor = .white
qrcodeBackView.layer.cornerRadius = 10
qrcodeBackView.layer.shadowColor = UIColor.black.cgColor
qrcodeBackView.layer.shadowRadius = 3
qrcodeBackView.layer.shadowOpacity = 0.1
qrcodeBackView.layer.shadowOffset = CGSize(width: 0, height: 0)
qrcodeBackView.layer.masksToBounds = false
qrcodeTopView.backgroundColor = #colorLiteral(red: 0.9877077937, green: 0.9827327132, blue: 0.8808727264, alpha: 1)
qrcodeTopView.layer.cornerRadius = 10
qrcodeTopView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
// private stack view
privatetextLabel.text = "개인안심번호"
privatetextLabel.font = UIFont.systemFont(ofSize: 15)
privateQuestionButton.setImage(UIImage(systemName: "questionmark.circle.fill"), for: .normal)
privateQuestionButton.tintColor = .gray
privateQuestionButton.setPreferredSymbolConfiguration(.init(pointSize: 15), forImageIn: .normal)
privateNumberLabel.text = "12현34규"
privateNumberLabel.font = UIFont.boldSystemFont(ofSize: 15)
var privateViewList = [UIView]()
privateViewList.append(contentsOf: [privatetextLabel, privateQuestionButton, privateNumberLabel])
_ = privateViewList.map {
privateStackView.addArrangedSubview($0)
}
privateStackView.axis = .horizontal
privateStackView.spacing = 5
privateStackView.alignment = .fill
privateStackView.distribution = .equalSpacing
timeLabel.font = UIFont.systemFont(ofSize: 15)
timeLabel.textColor = .gray
viewModel.setTimeText()
}
// ✅ SnapKit 을 활용한 오토레이아웃 잡기
private func setLayout() {
// ✅ 여러개의 UIView 를 한번에 등록하는 addSubviews() 커스텀 메서드
view.addSubviews([titleLabel, subtitleLabel, closeButton, switchShakeButton, qrcodeBackView])
qrcodeBackView.addSubviews([qrcodeTopView, qrcodeImageBackView, timeLabel])
qrcodeTopView.addSubviews([privateStackView])
qrcodeImageBackView.addSubviews([qrcodeImageView])
let guide = view.safeAreaLayoutGuide
titleLabel.snp.makeConstraints {
$0.centerX.equalToSuperview()
$0.top.equalTo(guide).offset(70)
}
subtitleLabel.snp.makeConstraints {
$0.centerX.equalToSuperview()
$0.top.equalTo(titleLabel.snp.bottom).offset(8)
}
closeButton.snp.makeConstraints {
$0.top.equalTo(guide).offset(20)
$0.right.equalTo(guide).offset(-20)
}
switchShakeButton.snp.makeConstraints {
$0.bottom.equalTo(guide).offset(-20)
$0.centerX.equalToSuperview()
}
qrcodeTopView.snp.makeConstraints {
$0.top.left.right.equalTo(qrcodeBackView)
$0.height.equalTo(40)
}
qrcodeBackView.snp.makeConstraints {
$0.top.equalTo(subtitleLabel.snp.bottom).offset(16)
$0.bottom.equalTo(switchShakeButton.snp.top).offset(-140)
$0.left.right.equalToSuperview().inset(16)
}
privateStackView.snp.makeConstraints {
$0.height.equalTo(30)
$0.left.right.equalTo(qrcodeBackView).inset(80)
$0.centerY.equalTo(qrcodeTopView)
}
qrcodeImageBackView.snp.makeConstraints {
$0.top.equalTo(qrcodeTopView.snp.bottom).offset(16)
$0.left.right.equalTo(qrcodeBackView).inset(60)
$0.height.equalTo(223)
}
qrcodeImageView.snp.makeConstraints {
$0.edges.equalTo(qrcodeImageBackView)
}
timeLabel.snp.makeConstraints {
$0.top.equalTo(qrcodeImageBackView.snp.bottom).offset(8)
$0.centerX.equalToSuperview()
}
}
// ✅ Binding
private func setBinding() {
viewModel.isShakeAvailable.bind { available in
self.isShakeAvailable = available
}
viewModel.shakeText.bind { text in
self.switchShakeButton.setTitle(text, for: .normal)
let attributeString = NSMutableAttributedString(string: text)
attributeString.addAttribute(.underlineStyle , value: 1 , range: NSRange.init(location: 0, length: text.count))
self.switchShakeButton.titleLabel?.attributedText = attributeString
}
viewModel.shakeImg.bind { image in
self.switchShakeButton.setImage(UIImage(systemName: image), for: .normal)
}
viewModel.qrcodeMsg.bind { msg in
self.qrcodeImageView.generateCode(msg)
}
viewModel.timeText.bind { time in
let text = "남은 시간 \(time)초"
let attributeStrring = NSMutableAttributedString(string: text)
attributeStrring.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange.init(location: 6, length: String(time).count+1))
self.timeLabel.attributedText = attributeStrring
}
}
// ✅ screenshot block
private func setNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(blockScreenShot), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(blockScreenShot), name: UIScreen.capturedDidChangeNotification, object: nil)
}
@objc
func blockScreenShot() {
let alert = UIAlertController(title: "캡쳐는 안돼요!", message: "보안 정책에 따라 스크린샷을 캡쳐할 수 없습니다.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
Observable
import Foundation
class Observable<T> {
typealias Listener = (T) -> Void
var listener: Listener?
var value: T {
didSet {
listener?(value)
}
}
init(_ value: T) {
self.value = value
}
func bind(_ closure: @escaping (T) -> Void) {
self.listener = closure
listener?(value)
}
}
View Model
import Foundation
import UIKit
public class QRCodeViewModel {
let isShakeAvailable = Observable(true)
let shakeText = Observable("QR 체크인 쉐이크 기능 끄기")
let shakeImg = Observable("iphone.slash")
let qrcodeMsg = Observable("https://gyuios.tistory.com/78")
let timeText = Observable(15)
func setShakeAvailable() {
self.isShakeAvailable.value = !isShakeAvailable.value
}
func setShakeText() {
if self.isShakeAvailable.value {
self.shakeText.value = "QR 체크인 쉐이크 기능 끄기"
} else {
self.shakeText.value = "QR 체크인 쉐이크 기능 켜기"
}
}
func setShakeImage() {
if self.isShakeAvailable.value {
self.shakeImg.value = "iphone.slash"
} else {
self.shakeImg.value = "iphone.radiowaves.left.and.right"
}
}
func setQrcodeMsg() {
self.qrcodeMsg.value = "https://gyuios.tistory.com/78"
}
func setTimeText() {
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
@objc
func timerAction() {
if self.timeText.value == 0 {
self.timeText.value = 15
// ✅ 15초 후에 qrcode 이미지가 변경되도록 함
self.qrcodeMsg.value = "https://gyuios.tistory.com/79"
} else {
self.timeText.value -= 1
}
}
// MARK: - presentation methods
func dismissToMainVC(_ view: UIViewController) {
view.dismiss(animated: true, completion: nil)
}
}
Model
특별히 사용할 부분은 없었다.
깃허브
728x90
반응형
'iOS > Clone Coding' 카테고리의 다른 글
iOS) Kakao QRcode Widget 클론코딩 - Widget(2/2) (0) | 2021.09.04 |
---|---|
iOS) Kakao QRcode Widget 클론코딩 - Widget(1/2) (2) | 2021.09.04 |
iOS) Kakao QRcode Widget 클론코딩 - 화면캡처 block (0) | 2021.09.01 |
iOS) Kakao QRcode Widget 클론코딩 - 진동(1/2) - Vibrate (0) | 2021.08.30 |
iOS) Kakao QRcode Widget 클론코딩 - shake motion event(2/2) - CMMotionManager (0) | 2021.08.29 |
댓글
TAG
- UserDefaults
- Protocol
- WWDC
- WidgetKit
- WWDC22
- MVVM
- MOYA
- github
- containerBackground
- IOS
- Widget
- RxCocoa
- Firebase
- 서버통신
- Swift
- configurable widget
- urlsession
- watchOS
- Notification
- SwiftUI
- YPImagePicker
- async/await
- Algorithm
- 2022 KAKAO TECH INTERNSHIP
- rxswift
- CloneCoding
- APNS
- Objective-C
- OpenSourceLibrary
- projectsetting
최근에 올라온 글
최근에 달린 댓글
글 보관함
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
링크
- Total
- Today
- Yesterday