diff --git a/momoIOS.xcodeproj/project.pbxproj b/momoIOS.xcodeproj/project.pbxproj index 1176ff2..b005022 100644 --- a/momoIOS.xcodeproj/project.pbxproj +++ b/momoIOS.xcodeproj/project.pbxproj @@ -7,15 +7,20 @@ objects = { /* Begin PBXBuildFile section */ - 3A5AC30A2989784A0080323A /* AbsenceModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5AC3092989784A0080323A /* AbsenceModalViewController.swift */; }; 3A5AC31029898B330080323A /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5AC30F29898B330080323A /* UIButton+Extension.swift */; }; 3A75FC10298827BE00B36A46 /* MainAttendanceCodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A75FC0F298827BE00B36A46 /* MainAttendanceCodeCell.swift */; }; + 3A8E2DDF2997697E00D5476A /* AttendanceCodeDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8E2DDE2997697E00D5476A /* AttendanceCodeDetailViewController.swift */; }; + 3A8E2DE129976A8F00D5476A /* AbsenceModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8E2DE029976A8F00D5476A /* AbsenceModalViewController.swift */; }; 3AA713C929884007006F922F /* MainAttendanceDoneCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA713C829884007006F922F /* MainAttendanceDoneCell.swift */; }; 3AA713D1298946FF006F922F /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA713D0298946FF006F922F /* UIColor+Extension.swift */; }; 3ABBF74829975801004D4D0B /* NotActiveSessionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABBF74729975801004D4D0B /* NotActiveSessionCell.swift */; }; 3ABBF75129975EDA004D4D0B /* AdminSessionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABBF74E29975ED9004D4D0B /* AdminSessionCell.swift */; }; 3ABBF75229975EDA004D4D0B /* AdminSessionTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABBF74F29975ED9004D4D0B /* AdminSessionTableViewController.swift */; }; 3ABBF75329975EDA004D4D0B /* AddSessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABBF75029975ED9004D4D0B /* AddSessionViewController.swift */; }; + 3ABEDB7129976EF100F9BF1C /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABEDB6F29976EF100F9BF1C /* UINavigationController+Extension.swift */; }; + 3ABEDB7229976EF100F9BF1C /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABEDB7029976EF100F9BF1C /* Optional+Extension.swift */; }; + 3ABEDB742997719000F9BF1C /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABEDB732997719000F9BF1C /* String+Extension.swift */; }; + 3ABEDB76299771A900F9BF1C /* Collection+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABEDB75299771A900F9BF1C /* Collection+Extension.swift */; }; BF0408632987B3AA00F1129B /* LoginController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0408622987B3AA00F1129B /* LoginController.swift */; }; BF04086B2987E34800F1129B /* AuthCommonConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF04086A2987E34800F1129B /* AuthCommonConstants.swift */; }; BF04086D2987E38C00F1129B /* RegistrationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF04086C2987E38C00F1129B /* RegistrationController.swift */; }; @@ -47,15 +52,20 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 3A5AC3092989784A0080323A /* AbsenceModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsenceModalViewController.swift; sourceTree = ""; }; 3A5AC30F29898B330080323A /* UIButton+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = ""; }; 3A75FC0F298827BE00B36A46 /* MainAttendanceCodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAttendanceCodeCell.swift; sourceTree = ""; }; + 3A8E2DDE2997697E00D5476A /* AttendanceCodeDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttendanceCodeDetailViewController.swift; sourceTree = ""; }; + 3A8E2DE029976A8F00D5476A /* AbsenceModalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbsenceModalViewController.swift; sourceTree = ""; }; 3AA713C829884007006F922F /* MainAttendanceDoneCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAttendanceDoneCell.swift; sourceTree = ""; }; 3AA713D0298946FF006F922F /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; 3ABBF74729975801004D4D0B /* NotActiveSessionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotActiveSessionCell.swift; sourceTree = ""; }; 3ABBF74E29975ED9004D4D0B /* AdminSessionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdminSessionCell.swift; sourceTree = ""; }; 3ABBF74F29975ED9004D4D0B /* AdminSessionTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdminSessionTableViewController.swift; sourceTree = ""; }; 3ABBF75029975ED9004D4D0B /* AddSessionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddSessionViewController.swift; sourceTree = ""; }; + 3ABEDB6F29976EF100F9BF1C /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; + 3ABEDB7029976EF100F9BF1C /* Optional+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = ""; }; + 3ABEDB732997719000F9BF1C /* String+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; + 3ABEDB75299771A900F9BF1C /* Collection+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Extension.swift"; sourceTree = ""; }; BF0408622987B3AA00F1129B /* LoginController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginController.swift; sourceTree = ""; }; BF04086A2987E34800F1129B /* AuthCommonConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCommonConstants.swift; sourceTree = ""; }; BF04086C2987E38C00F1129B /* RegistrationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationController.swift; sourceTree = ""; }; @@ -99,6 +109,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3A8E2DDC2997697E00D5476A /* AttendanceCodeDetail */ = { + isa = PBXGroup; + children = ( + 3A8E2DDE2997697E00D5476A /* AttendanceCodeDetailViewController.swift */, + ); + path = AttendanceCodeDetail; + sourceTree = ""; + }; 3ABBF73F29975082004D4D0B /* Common */ = { isa = PBXGroup; children = ( @@ -111,7 +129,7 @@ 3ABBF742299750A4004D4D0B /* UI */ = { isa = PBXGroup; children = ( - 3A5AC3092989784A0080323A /* AbsenceModalViewController.swift */, + 3A8E2DE029976A8F00D5476A /* AbsenceModalViewController.swift */, BF43B57729951F0C0026DCE3 /* ProfileView.swift */, ); path = UI; @@ -145,6 +163,7 @@ isa = PBXGroup; children = ( 3ABBF743299752EB004D4D0B /* Main */, + 3A8E2DDC2997697E00D5476A /* AttendanceCodeDetail */, BF9988FA298AD002005723C7 /* PersonalInformation */, ); path = UserSide; @@ -220,8 +239,12 @@ E8207930298A163400B36FC9 /* Extensions */ = { isa = PBXGroup; children = ( + 3ABEDB75299771A900F9BF1C /* Collection+Extension.swift */, + 3ABEDB7029976EF100F9BF1C /* Optional+Extension.swift */, + 3ABEDB732997719000F9BF1C /* String+Extension.swift */, 3A5AC30F29898B330080323A /* UIButton+Extension.swift */, 3AA713D0298946FF006F922F /* UIColor+Extension.swift */, + 3ABEDB6F29976EF100F9BF1C /* UINavigationController+Extension.swift */, BFB8431E298CFF9200BA11EC /* UIStackView+Extension.swift */, E8207931298A163400B36FC9 /* UIView+Extension.swift */, ); @@ -373,14 +396,15 @@ E8D5C3F32984FC23003D1AD0 /* SceneDelegate.swift in Sources */, 3A75FC10298827BE00B36A46 /* MainAttendanceCodeCell.swift in Sources */, E83238F1298506A000DC83C2 /* MainViewController.swift in Sources */, - 3A5AC30A2989784A0080323A /* AbsenceModalViewController.swift in Sources */, BF998900298BB0D3005723C7 /* InputMemberInfoController.swift in Sources */, BF0408632987B3AA00F1129B /* LoginController.swift in Sources */, BF6EC77F298D1B2E00F4B170 /* AttendanceResultCell.swift in Sources */, BF04086B2987E34800F1129B /* AuthCommonConstants.swift in Sources */, 3A5AC31029898B330080323A /* UIButton+Extension.swift in Sources */, BF43B57829951F0C0026DCE3 /* ProfileView.swift in Sources */, + 3A8E2DDF2997697E00D5476A /* AttendanceCodeDetailViewController.swift in Sources */, E8207934298A22D000B36FC9 /* MainSessionInfoCell.swift in Sources */, + 3A8E2DE129976A8F00D5476A /* AbsenceModalViewController.swift in Sources */, 3ABBF74829975801004D4D0B /* NotActiveSessionCell.swift in Sources */, 3ABBF75229975EDA004D4D0B /* AdminSessionTableViewController.swift in Sources */, E8207932298A163400B36FC9 /* UIView+Extension.swift in Sources */, @@ -390,11 +414,15 @@ E820792B298A119900B36FC9 /* MainSessionDetailCell.swift in Sources */, E8207936298A248200B36FC9 /* MainSessionNoInfoCell.swift in Sources */, BF43B57629951EAF0026DCE3 /* MoimManagementController.swift in Sources */, + 3ABEDB742997719000F9BF1C /* String+Extension.swift in Sources */, 3AA713D1298946FF006F922F /* UIColor+Extension.swift in Sources */, 3AA713C929884007006F922F /* MainAttendanceDoneCell.swift in Sources */, BF43B57A29952AFF0026DCE3 /* MoimSettingCell.swift in Sources */, E820792F298A138500B36FC9 /* MainSessionAbsentCell.swift in Sources */, BF4310FA298C525900270DBF /* AttendanceHistoryCell.swift in Sources */, + 3ABEDB76299771A900F9BF1C /* Collection+Extension.swift in Sources */, + 3ABEDB7129976EF100F9BF1C /* UINavigationController+Extension.swift in Sources */, + 3ABEDB7229976EF100F9BF1C /* Optional+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/momoIOS/Common/Extensions/Collection+Extension.swift b/momoIOS/Common/Extensions/Collection+Extension.swift new file mode 100644 index 0000000..cb90958 --- /dev/null +++ b/momoIOS/Common/Extensions/Collection+Extension.swift @@ -0,0 +1,14 @@ +// +// Collection+Extension.swift +// momoIOS +// +// Created by 임수현 on 2023/02/11. +// + +import Foundation + +extension Collection { + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/momoIOS/Common/Extensions/Optional+Extension.swift b/momoIOS/Common/Extensions/Optional+Extension.swift new file mode 100644 index 0000000..2be162c --- /dev/null +++ b/momoIOS/Common/Extensions/Optional+Extension.swift @@ -0,0 +1,23 @@ +// +// Optional+Extension.swift +// momoIOS +// +// Created by 임수현 on 2023/02/11. +// + +import Foundation + +extension Optional { + var isNil: Bool { + self == nil + } + var isNotNil: Bool { + self != nil + } +} + +extension String? { + var isEmptyOrNil: Bool { + self?.isEmpty == true || self.isNil + } +} diff --git a/momoIOS/Common/Extensions/String+Extension.swift b/momoIOS/Common/Extensions/String+Extension.swift new file mode 100644 index 0000000..d1872ce --- /dev/null +++ b/momoIOS/Common/Extensions/String+Extension.swift @@ -0,0 +1,40 @@ +// +// String+Extension.swift +// momoIOS +// +// Created by 임수현 on 2023/02/11. +// + +import Foundation + +extension String { + subscript (safe index: Int) -> String? { + guard self.count > index else { return nil } + let index = self.index(self.startIndex, offsetBy: index) + return "\(self[index])" + } + + subscript (range: Range) -> String? { + let fromIndex = self.index(self.startIndex, offsetBy: max(0, range.lowerBound)) + let toIndex = self.index(self.startIndex, offsetBy: min(range.upperBound, self.count)) + return String(self[fromIndex..) -> String { + let fromIndex = self.index(self.startIndex, offsetBy: range.startIndex) + let toIndex = self.index(self.startIndex, offsetBy: range.endIndex) + return String(self[fromIndex.. String? { + guard let range = Range(range, in: self) else { return nil } + return self.replacingCharacters(in: range, with: string) + } + + var isBackspace: Bool { + if let char = self.cString(using: String.Encoding.utf8) { + return strcmp(char, "\\b") == -92 + } + return false + } +} diff --git a/momoIOS/Common/Extensions/UINavigationController+Extension.swift b/momoIOS/Common/Extensions/UINavigationController+Extension.swift new file mode 100644 index 0000000..cc2658b --- /dev/null +++ b/momoIOS/Common/Extensions/UINavigationController+Extension.swift @@ -0,0 +1,21 @@ +// +// UINavigationController+Extension.swift +// momoIOS +// +// Created by 임수현 on 2023/02/11. +// + +import UIKit + +extension UINavigationController: ObservableObject, UIGestureRecognizerDelegate { + override open func viewDidLoad() { + super.viewDidLoad() + + // backButton을 임의로 변경하면 swipe back 동작이 되지 않기 떄문에 gestureRecognizer를 설정하여 동작하도록 함. + interactivePopGestureRecognizer?.delegate = self + } + + public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + return viewControllers.count > 1 + } +} diff --git a/momoIOS/Common/Extensions/UIView+Extension.swift b/momoIOS/Common/Extensions/UIView+Extension.swift index db1abd7..194dc99 100644 --- a/momoIOS/Common/Extensions/UIView+Extension.swift +++ b/momoIOS/Common/Extensions/UIView+Extension.swift @@ -13,4 +13,25 @@ extension UIView { func addSubviews(_ views: UIView...) { views.forEach { addSubview($0) } } + + /// 키보드 움직임에 따라서 움직이도록 함. + func moveWithKeyboard(willShow keyboardWillShow: Bool, notification: NSNotification, safeAreaBottomInset: CGFloat = 0) { + guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, + let keyboardDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double, + let keyboardcurveValue = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt + else { return } + + let keyboardHeight = keyboardSize.height + let curveOption = UIView.AnimationOptions(rawValue: keyboardcurveValue << 16) + + if keyboardWillShow { + UIView.animate(withDuration: keyboardDuration, delay: 0, options: [curveOption]) { [weak self] in + self?.transform = CGAffineTransform(translationX: 0, y: -keyboardHeight + safeAreaBottomInset) + } + } else { + UIView.animate(withDuration: keyboardDuration, delay: 0, options: [curveOption]) { [weak self] in + self?.transform = .identity + } + } + } } diff --git a/momoIOS/Main/Cell/MainAttendanceCodeCell.swift b/momoIOS/Main/Cell/MainAttendanceCodeCell.swift new file mode 100644 index 0000000..165b077 --- /dev/null +++ b/momoIOS/Main/Cell/MainAttendanceCodeCell.swift @@ -0,0 +1,89 @@ +// +// MainAttendanceCodeCell.swift +// momoIOS +// +// Created by 임수현 on 2023/01/31. +// + +import UIKit +import SnapKit + +class MainAttendanceCodeCell: UITableViewCell { + private let cardView: UIView = UIView() + private let titleLabel: UILabel = UILabel() + private let codeStackView: UIStackView = UIStackView() + private let descriptionLabel: UILabel = UILabel() + + // MARK: - Initializer + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: "MainAttendanceCodeCell") + + self.selectionStyle = .none + self.setupViews() + self.setupLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + // MARK: - Setup + private func setupViews() { + // TODO: ViewModel 바라보도록 수정 필요 + self.cardView.backgroundColor = .rgba(128, 135, 201, 1) + self.cardView.layer.cornerRadius = 12 + self.contentView.addSubview(self.cardView) + + self.titleLabel.text = "출석체크 코드 입력" + self.titleLabel.font = .systemFont(ofSize: 17, weight: .medium) + self.titleLabel.textColor = .white + self.cardView.addSubview(self.titleLabel) + + self.codeStackView.axis = .horizontal + self.codeStackView.alignment = .center + self.codeStackView.spacing = 14 + for _ in 0..<4 { + let lineView = UIView() + lineView.backgroundColor = .white + self.codeStackView.addArrangedSubview(lineView) + } + self.cardView.addSubview(self.codeStackView) + + self.descriptionLabel.text = "운영진이 공지해준 출석체크 코드를 입력해주세요!" + self.descriptionLabel.font = .systemFont(ofSize: 14) + self.descriptionLabel.textColor = .white.withAlphaComponent(0.67) + self.cardView.addSubview(self.descriptionLabel) + } + + // MARK: - Layout + private func setupLayout() { + self.cardView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(24) + make.bottom.equalToSuperview().inset(19) + } + + self.titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(28) + make.centerX.equalToSuperview() + } + + self.codeStackView.snp.makeConstraints { make in + make.top.equalTo(self.titleLabel.snp.bottom).offset(103) + make.centerX.equalToSuperview() + } + + self.codeStackView.arrangedSubviews.forEach { + $0.snp.makeConstraints { make in + make.height.equalTo(1) + make.width.equalTo(44) + } + } + + self.descriptionLabel.snp.makeConstraints { make in + make.top.equalTo(self.codeStackView.snp.bottom).offset(26) + make.bottom.equalToSuperview().inset(35) + make.centerX.equalToSuperview() + } + } +} diff --git a/momoIOS/Main/Cell/MainAttendanceDoneCell.swift b/momoIOS/Main/Cell/MainAttendanceDoneCell.swift new file mode 100644 index 0000000..03301b3 --- /dev/null +++ b/momoIOS/Main/Cell/MainAttendanceDoneCell.swift @@ -0,0 +1,144 @@ +// +// MainAttendanceDoneCell.swift +// momoIOS +// +// Created by 임수현 on 2023/01/31. +// + +import UIKit +import SnapKit + +class MainAttendanceDoneCell: UITableViewCell { + private let cardView: UIView = UIView() + private var iconImageView: UIImageView = UIImageView() + private let titleLabel: UILabel = UILabel() + private let timeStatusContainerView: UIView = UIView() + private let timeLabel: UILabel = UILabel() + private let statusLabel: UILabel = UILabel() + private let scoreLabel: UILabel = UILabel() + private let historyButton: UIButton = UIButton() + + var historyButtonAction: (() -> Void)? + + // MARK: - Initializer + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: "MainAttendanceDoneCell") + + self.selectionStyle = .none + self.setupViews() + self.setupLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + // MARK: - Setup + // TODO: ViewModel 바라보도록 수정 필요 + private func setupViews() { + self.cardView.backgroundColor = .rgba(128, 135, 201, 1) + self.cardView.layer.cornerRadius = 12 + self.contentView.addSubview(self.cardView) + + self.setupIconImageView() + self.setupTitleLabel() + self.setupTimeStatusViews() + self.setupScoreLabel() + self.setupHistoryButton() + } + + private func setupIconImageView() { + let checkImage = UIImage(systemName: "checkmark.circle") + self.iconImageView.image = checkImage + self.iconImageView.tintColor = .white + self.cardView.addSubview(self.iconImageView) + } + + private func setupTitleLabel() { + self.titleLabel.text = "출석이 완료되었습니다" + self.titleLabel.font = .systemFont(ofSize: 17, weight: .medium) + self.titleLabel.textColor = .white + self.cardView.addSubview(self.titleLabel) + } + + private func setupTimeStatusViews() { + self.timeLabel.text = "2023.01.07 pm 1:30" + self.timeLabel.font = .systemFont(ofSize: 13, weight: .medium) + self.timeLabel.textColor = .white + self.timeStatusContainerView.addSubview(self.timeLabel) + + self.statusLabel.text = "지각😞" + self.statusLabel.font = .systemFont(ofSize: 13, weight: .medium) + self.statusLabel.textColor = .white + self.timeStatusContainerView.addSubview(self.statusLabel) + + self.cardView.addSubview(self.timeStatusContainerView) + } + + private func setupScoreLabel() { + self.scoreLabel.text = "(-5점이 감점되었습니다)" + self.scoreLabel.font = .systemFont(ofSize: 13, weight: .regular) + self.scoreLabel.textColor = .white + self.cardView.addSubview(self.scoreLabel) + } + + private func setupHistoryButton() { + self.historyButton.addTarget(self, action: #selector(didTapHistoryButton), for: .touchUpInside) + self.historyButton.setTitle("출석 히스토리", size: 12, weight: .medium, color: .white) + self.historyButton.configurate(bgColor: .white.withAlphaComponent(0.14), cornerRadius: 6, padding: 10) + self.cardView.addSubview(self.historyButton) + } + + // MARK: - Layout + private func setupLayout() { + self.cardView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(24) + make.bottom.equalToSuperview().inset(19) + } + + self.iconImageView.snp.makeConstraints { make in + make.top.equalToSuperview().inset(31) + make.size.equalTo(42) + make.centerX.equalToSuperview() + } + + self.titleLabel.snp.makeConstraints { make in + make.top.equalTo(self.iconImageView.snp.bottom).offset(31) + make.centerX.equalToSuperview() + } + + self.setupTimeStatusLabelsLayout() + self.timeStatusContainerView.snp.makeConstraints { make in + make.top.equalTo(self.titleLabel.snp.bottom).offset(10) + make.centerX.equalToSuperview() + } + + self.scoreLabel.snp.makeConstraints { make in + make.top.equalTo(self.timeStatusContainerView.snp.bottom).offset(7) + make.centerX.equalToSuperview() + } + + self.historyButton.snp.makeConstraints { make in + make.top.equalTo(self.scoreLabel.snp.bottom).offset(23) + make.bottom.equalToSuperview().inset(30) + make.centerX.equalToSuperview() + } + } + + private func setupTimeStatusLabelsLayout() { + self.timeLabel.snp.makeConstraints { make in + make.leading.top.bottom.equalToSuperview() + } + + self.statusLabel.snp.makeConstraints { make in + make.leading.equalTo(self.timeLabel.snp.trailing).offset(7) + make.trailing.top.bottom.equalToSuperview() + } + } + + // MARK: - Actions + @objc private func didTapHistoryButton() { + self.historyButtonAction?() + } +} diff --git a/momoIOS/Main/ViewController/MainViewController.swift b/momoIOS/Main/ViewController/MainViewController.swift new file mode 100644 index 0000000..900425c --- /dev/null +++ b/momoIOS/Main/ViewController/MainViewController.swift @@ -0,0 +1,132 @@ +// +// MainViewController.swift +// momoIOS +// +// Created by JOSUEYEON on 2023/01/28. +// + +import UIKit +import SnapKit + +class MainViewController: UIViewController { + private let tableView: UITableView = UITableView(frame: .zero) + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + + self.setupTableView() + self.setupLayout() + self.setNavCustom() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.navigationController?.setNavigationBarHidden(false, animated: animated) + } + + // MARK: - navigation controller custom + private func setNavCustom() { + self.navigationController?.navigationBar.tintColor = .black + self.navigationItem.hidesBackButton = true + let location = UIBarButtonItem(image: UIImage(systemName: "location.circle"), style: .plain, target: nil, action: nil) + let myLocation = UIBarButtonItem(title: "서울특별시 강남구 역삼로", style: .plain, target: nil, action: nil) + let GPS = UIBarButtonItem(image: UIImage(systemName: "scope"), style: .plain, target: nil, action: nil) + let profile = UIBarButtonItem(image: UIImage(systemName: "person.crop.circle.fill"), style: .plain, target: self, action: #selector(goToUserProfileVC)) + + self.navigationItem.leftBarButtonItems = [location, myLocation, GPS] + self.navigationItem.rightBarButtonItem = profile + } + + // MARK: - Setup + private func setupTableView() { + self.tableView.delegate = self + self.tableView.dataSource = self + self.registerCells() + self.view.addSubview(tableView) + } + + private func registerCells() { + self.tableView.register(MainAttendanceCodeCell.self, forCellReuseIdentifier: "MainAttendanceCodeCell") + self.tableView.register(MainAttendanceDoneCell.self, forCellReuseIdentifier: "MainAttendanceDoneCell") + self.tableView.register(MainSessionTimeCell.self, forCellReuseIdentifier: "MainSessionTimeCell") + self.tableView.register(MainSessionDetailCell.self, forCellReuseIdentifier: "MainSessionDetailCell") + self.tableView.register(MainSessionInfoCell.self, forCellReuseIdentifier: "MainSessionInfoCell") + self.tableView.register(MainSessionAbsentCell.self, forCellReuseIdentifier: "MainSessionAbsentCell") + } + + // MARK: - Layout + private func setupLayout() { + self.tableView.separatorStyle = .none + self.tableView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + // MARK: - Action + private func goToAttendanceVC() { + let attandanceVC = AttendanceDetailViewController() + self.navigationController?.pushViewController(attandanceVC, animated: true) + } + + private func showAbsenceModal() { + let absenceModal = AbsenceModalViewController() + absenceModal.modalPresentationStyle = .overFullScreen + self.present(absenceModal, animated: true) + } + + @objc private func goToUserProfileVC() { + let controller = PersonalInformationController() + self.navigationController?.pushViewController(controller, animated: true) + } +} + +extension MainViewController: UITableViewDelegate, UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return 6 + } + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + switch indexPath.section { + case 0: + return tableView.dequeueReusableCell(withIdentifier: "MainSessionTimeCell") ?? UITableViewCell() + case 1: + return tableView.dequeueReusableCell(withIdentifier: "MainAttendanceCodeCell") ?? UITableViewCell() + case 2: + guard let cell = tableView.dequeueReusableCell(withIdentifier: "MainAttendanceDoneCell") as? MainAttendanceDoneCell else { + return UITableViewCell() + } + cell.historyButtonAction = { [weak self] in + self?.goToUserProfileVC() + } + return cell + case 3: + return tableView.dequeueReusableCell(withIdentifier: "MainSessionDetailCell") ?? UITableViewCell() + case 4: + return tableView.dequeueReusableCell(withIdentifier: "MainSessionInfoCell") ?? UITableViewCell() + case 5: + guard let cell = tableView.dequeueReusableCell(withIdentifier: "MainSessionAbsentCell") as? MainSessionAbsentCell else { + return UITableViewCell() + } + cell.seesionAbsentBtnAction = { [weak self] in + self?.showAbsenceModal() + } + return cell + default: + return UITableViewCell() + } + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + switch indexPath.section { + case 1: // (MainAttendanceCodeCell) 출석 코드 입력 셀 + self.goToAttendanceVC() + default: + break + } + } +} diff --git a/momoIOS/UserSide/AttendanceCodeDetail/AttendanceCodeDetailViewController.swift b/momoIOS/UserSide/AttendanceCodeDetail/AttendanceCodeDetailViewController.swift new file mode 100644 index 0000000..b291188 --- /dev/null +++ b/momoIOS/UserSide/AttendanceCodeDetail/AttendanceCodeDetailViewController.swift @@ -0,0 +1,282 @@ +// +// AttendanceCodeDetailViewController.swift +// momoIOS +// +// Created by 임수현 on 2023/02/09. +// + +import UIKit + +class AttendanceCodeDetailViewController: UIViewController { + private let backgroundView: UIView = UIView() + private let titleLabel: UILabel = UILabel() + private let codeContainerView: UIStackView = UIStackView() + private let codeTextFields: [UITextField] = [UITextField(), UITextField(), UITextField(), UITextField()] + private let descriptionLabel: UILabel = UILabel() + private let attendButton: UIButton = UIButton() + + private var inputCodeString: String { + var string = "" + self.codeTextFields.forEach { textField in + string.append(textField.text ?? " ") + } + return string + } + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + + self.setupKeyboardNotifications() + self.setupViews() + self.setupLayout() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.navigationController?.setNavigationBarHidden(false, animated: animated) + self.setupNavigation() + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.view.endEditing(true) + } + + // MARK: - Setup + private func setupKeyboardNotifications() { + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillShow(_:)), + name: UIResponder.keyboardWillShowNotification, + object: nil) + + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillHide(_:)), + name: UIResponder.keyboardWillHideNotification, + object: nil) + } + + private func setupNavigation() { + let navigationBar = self.navigationController?.navigationBar + let appearance = navigationBar?.standardAppearance ?? UINavigationBarAppearance() + appearance.shadowColor = .rgba(24, 24, 24, 0.16) + appearance.backgroundColor = .white.withAlphaComponent(0.96) + navigationBar?.standardAppearance = appearance + + let backButtonImage = UIImage(systemName: "arrow.left") + let backBarButton = UIBarButtonItem(image: backButtonImage, style: .plain, target: self, action: #selector(popViewController)) + self.navigationItem.leftBarButtonItem = backBarButton + + let titleLabel = UILabel() + titleLabel.text = "서울특별시 강남구 역삼로" + titleLabel.textColor = .black + titleLabel.font = .systemFont(ofSize: 14) + self.navigationItem.titleView = titleLabel + } + + private func setupViews() { + self.view.backgroundColor = .white + + self.backgroundView.backgroundColor = .rgba(128, 135, 201, 1) + self.backgroundView.layer.cornerRadius = 12 + + self.titleLabel.text = "출석체크 코드 입력" + self.titleLabel.font = .systemFont(ofSize: 17, weight: .bold) + self.titleLabel.textColor = .white + + self.codeContainerView.axis = .horizontal + self.codeContainerView.spacing = 14 + self.codeContainerView.distribution = .fillEqually + + self.codeTextFields.forEach { textField in + textField.backgroundColor = .clear + textField.textAlignment = .center + textField.textColor = .white + textField.font = .systemFont(ofSize: 32, weight: .medium) + textField.keyboardType = .numberPad + textField.delegate = self + } + + self.descriptionLabel.text = "운영진이 공지해준 출석체크 코드를 입력해주세요!" + self.descriptionLabel.font = .systemFont(ofSize: 12) + self.descriptionLabel.textColor = .white.withAlphaComponent(0.67) + self.backgroundView.addSubview(self.descriptionLabel) + + self.attendButton.setTitle("출석하기", size: 16, weight: .bold, color: .white) + self.configureAttendButtonEnabled() + self.attendButton.addTarget(self, action: #selector(didTapAttendButton), for: .touchUpInside) + } + + // MARK: - Layout + private func setupLayout() { + self.view.addSubview(self.backgroundView) + self.backgroundView.snp.makeConstraints { make in + make.top.equalTo(self.view.safeAreaLayoutGuide).inset(30) + make.leading.trailing.equalTo(self.view.safeAreaLayoutGuide).inset(24) + } + + self.backgroundView.addSubview(self.titleLabel) + self.titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(35) + make.centerX.equalToSuperview() + } + + self.backgroundView.addSubview(self.codeContainerView) + self.codeContainerView.snp.makeConstraints { make in + make.top.equalTo(self.titleLabel.snp.bottom).offset(54) + make.leading.trailing.equalToSuperview().inset(53) + } + + self.codeTextFields.forEach { textField in + let codeView = UIStackView() + codeView.axis = .vertical + let view = UIView() + view.backgroundColor = .white + + codeView.addArrangedSubviews(textField, view) + textField.snp.makeConstraints { make in + make.width.equalTo(45) + make.height.equalTo(70) + } + view.snp.makeConstraints { make in + make.width.equalTo(textField) + make.height.equalTo(1) + } + + self.codeContainerView.addArrangedSubview(codeView) + } + self.backgroundView.addSubview(self.descriptionLabel) + self.descriptionLabel.snp.makeConstraints { make in + make.top.equalTo(self.codeContainerView.snp.bottom).offset(33) + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(42) + } + + self.view.addSubview(self.attendButton) + self.attendButton.snp.makeConstraints { make in + make.leading.trailing.equalTo(self.view.safeAreaLayoutGuide).inset(24) + make.bottom.equalTo(self.view.safeAreaLayoutGuide).inset(28) + make.height.equalTo(54) + } + } + + // MARK: - Actions + @objc private func didTapAttendButton() { + self.popViewController() + } + + private func configureAttendButtonEnabled() { + let isAttendButtonEnabled = { + for textField in self.codeTextFields where textField.text.isEmptyOrNil { + return false + } + return true + }() + + if isAttendButtonEnabled { + self.attendButton.isEnabled = true + self.attendButton.configurate(bgColor: .rgba(56, 56, 56, 1), cornerRadius: 7, padding: 10) + } else { + self.attendButton.isEnabled = false + self.attendButton.configurate(bgColor: .rgba(200, 200, 200, 1), cornerRadius: 7, padding: 10) + } + } + + @objc private func popViewController(animated: Bool = true) { + self.navigationController?.popViewController(animated: true) + } +} + +// MARK: - UITextFieldDelegate +extension AttendanceCodeDetailViewController: UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + defer { self.configureAttendButtonEnabled() } + + let currentText = textField.text ?? "" + let newText = range.lowerBound == 0 ? string : currentText.replaced(string, in: range) ?? "" + + if newText.count == 0 { + textField.text = newText + self.prevTextField(textField)?.becomeFirstResponder() + } else { + self.fillTextField(textField, with: newText) + } + return false + } + + // MARK: - TextField Values + private var isEditingCode: Bool { + for textField in self.codeTextFields where textField.isEditing == true { + return true + } + return false + } + + private func textFieldIndex(of textField: UITextField) -> Int? { + for index in 0.. UITextField? { + guard let index = self.textFieldIndex(of: textField) else { return nil } + return self.codeTextFields[safe: index - 1] + } + + private func nextTextField(_ textField: UITextField) -> UITextField? { + guard let index = self.textFieldIndex(of: textField) else { return nil } + return self.codeTextFields[safe: index + 1] + } + + // MARK: - TextField Actions + @objc private func keyboardWillShow(_ notification: NSNotification) { + if self.isEditingCode { + self.attendButton.moveWithKeyboard( + willShow: true, + notification: notification, + safeAreaBottomInset: self.view.safeAreaInsets.bottom + ) + } + } + + @objc private func keyboardWillHide(_ notification: NSNotification) { + self.attendButton.moveWithKeyboard( + willShow: false, + notification: notification, + safeAreaBottomInset: self.view.safeAreaInsets.bottom + ) + } + + private func fillTextField(_ textField: UITextField, with string: String) { + let string = string.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.").inverted) + guard !string.isEmpty else { return } + textField.text = string[safe: 0] + + guard let nextTextField = self.nextTextField(textField) else { return } + nextTextField.becomeFirstResponder() + if nextTextField.text?.isEmpty == false { + self.setCursor(of: nextTextField, at: 0) + } + self.fillTextField(nextTextField, with: string[1..