diff --git a/.gitignore b/.gitignore index d0030620..c003f369 100644 --- a/.gitignore +++ b/.gitignore @@ -85,7 +85,11 @@ Dependencies/ **/fastlane/report.xml **/fastlane/Preview.html **/fastlane/screenshots/**/*.png +**/fastlane/screenshots/ **/fastlane/BuildOutputs +**/fastlane/metadata +**/fastlane/Deliverfile + **/fastlane/.env.default diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 09b9b28d..215790c4 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -1703,7 +1703,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2024.0319.2120; + CURRENT_PROJECT_VERSION = 2024.0402.1647; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8Q4H7X3Q58; GENERATE_INFOPLIST_FILE = NO; @@ -1747,7 +1747,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2024.0319.2120; + CURRENT_PROJECT_VERSION = 2024.0402.1647; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8Q4H7X3Q58; GENERATE_INFOPLIST_FILE = NO; diff --git a/Runnect-iOS/Runnect-iOS/Global/Utils/Toast.swift b/Runnect-iOS/Runnect-iOS/Global/Utils/Toast.swift index d2b8dbc6..c9438bc3 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Utils/Toast.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Utils/Toast.swift @@ -2,16 +2,22 @@ // Toast.swift // Runnect-iOS // -// Created by sejin on 2022/12/31. +// Created by 이명진 on 2024/03/25. // import UIKit import SnapKit +import Then public extension UIViewController { - func showToast(message: String) { - Toast.show(message: message, view: self.view, safeAreaBottomInset: self.safeAreaBottomInset()) + func showToast(message: String, heightOffset: CGFloat = 0) { + Toast.show( + message: message, + view: self.view, + safeAreaBottomInset: self.safeAreaBottomInset(), + heightOffset: heightOffset + ) } func showNetworkFailureToast() { @@ -20,34 +26,39 @@ public extension UIViewController { } public class Toast { - public static func show(message: String, view: UIView, safeAreaBottomInset: CGFloat = 0) { - - let toastContainer = UIView() - let toastLabel = UILabel() - - toastContainer.backgroundColor = UIColor.g2.withAlphaComponent(0.7) - toastContainer.alpha = 1.0 - toastContainer.layer.cornerRadius = 15 - toastContainer.clipsToBounds = true - toastContainer.isUserInteractionEnabled = false + public static func show( + message: String, + view: UIView, + safeAreaBottomInset: CGFloat = 0, + heightOffset: CGFloat = 0 + ) { + let toastContainer = UIView().then { + $0.backgroundColor = UIColor.g2.withAlphaComponent(0.7) + $0.alpha = 1.0 + $0.layer.cornerRadius = 15 + $0.clipsToBounds = true + $0.isUserInteractionEnabled = false + } - toastLabel.textColor = .m4 - toastLabel.font = .b4 - toastLabel.textAlignment = .center - toastLabel.text = message - toastLabel.clipsToBounds = true - toastLabel.numberOfLines = 0 - toastLabel.sizeToFit() + let toastLabel = UILabel().then { + $0.textColor = .m4 + $0.font = .b4 + $0.textAlignment = .center + $0.text = message + $0.clipsToBounds = true + $0.numberOfLines = 0 + $0.sizeToFit() + } toastContainer.addSubview(toastLabel) view.addSubview(toastContainer) - let toastConatinerWidth = toastLabel.intrinsicContentSize.width + 40.0 + let toastContainerWidth = toastLabel.intrinsicContentSize.width + 40.0 toastContainer.snp.makeConstraints { $0.centerX.equalToSuperview() - $0.bottom.equalToSuperview().inset(safeAreaBottomInset+160) - $0.width.equalTo(toastConatinerWidth) + $0.bottom.equalToSuperview().inset(safeAreaBottomInset + 160 + heightOffset) + $0.width.equalTo(toastContainerWidth) $0.height.equalTo(31) } diff --git a/Runnect-iOS/Runnect-iOS/Info.plist b/Runnect-iOS/Runnect-iOS/Info.plist index d5e25111..bde14cac 100644 --- a/Runnect-iOS/Runnect-iOS/Info.plist +++ b/Runnect-iOS/Runnect-iOS/Info.plist @@ -21,7 +21,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.0.2 + 2.0.3 CFBundleURLTypes @@ -46,7 +46,7 @@ CFBundleVersion - 2024.0319.2120 + 2024.0402.1647 LSApplicationQueriesSchemes kakaokompassauth diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseUploadVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseUploadVC.swift index 773814b4..1c7e462b 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseUploadVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseUploadVC.swift @@ -45,7 +45,7 @@ class CourseUploadVC: UIViewController { } private let distanceInfoView = CourseDetailInfoView(title: "거리", description: "0.0km") private let departureInfoView = CourseDetailInfoView(title: "출발지", description: "") - private let placeholder = "코스에 대한 소개를 적어주세요.(난이도/풍경/지형)" + private let placeholder = "코스에 대한 소개를 적어주세요.(난이도/풍경/지형)\n(최대 150자)" let activityTextView = UITextView().then { $0.font = .b4 @@ -61,11 +61,10 @@ class CourseUploadVC: UIViewController { setNavigationBar() setUI() setLayout() - setupTextView() + setDelegate() setAddTarget() - setKeyboardNotification() setTapGesture() - addKeyboardObserver() + setKeyboardObservers() analyze(screenName: GAEvent.View.viewCourseUpload) } @@ -107,21 +106,6 @@ extension CourseUploadVC { self.uploadButton.addTarget(self, action: #selector(uploadButtonDidTap), for: .touchUpInside) } - // 키보드가 올라오면 scrollView 위치 조정 - private func setKeyboardNotification() { - 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 setTapGesture() { let tap = UITapGestureRecognizer(target: view, action: #selector(UIView.endEditing)) @@ -129,7 +113,14 @@ extension CourseUploadVC { view.addGestureRecognizer(tap) } - private func addKeyboardObserver() { + // 업로드 버튼 상태 업데이트 메소드 + private func updateUploadButtonState() { + let isTitleNotEmpty = !(courseTitleTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) + let isContentNotEmptyAndNotPlaceholder = !(activityTextView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || activityTextView.text == placeholder) + uploadButton.setEnabled(isTitleNotEmpty && isContentNotEmptyAndNotPlaceholder) + } + + private func setKeyboardObservers() { NotificationCenter.default.addObserver( self, selector: #selector(keyboardWillShow), @@ -146,19 +137,7 @@ extension CourseUploadVC { extension CourseUploadVC { @objc private func textFieldTextDidChange() { - guard let text = courseTitleTextField.text else { return } - - if text.count > courseTitleMaxLength { - let index = text.index(text.startIndex, offsetBy: courseTitleMaxLength) - let newString = text[text.startIndex.. 150 { activityTextView.deleteBackward() } } + func textViewDidEndEditing(_ textView: UITextView) { if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || textView.text == placeholder { activityTextView.textColor = .g3 @@ -332,6 +312,18 @@ extension CourseUploadVC: UITextViewDelegate { } } +// MARK: - UITextFieldDelegate + +extension CourseUploadVC: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if textField == courseTitleTextField { + activityTextView.becomeFirstResponder() + return true + } + return false + } +} + // MARK: - Network extension CourseUploadVC { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/MyPageVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/MyPageVC.swift index 41d99a07..a15c360f 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/MyPageVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/MyPageVC.swift @@ -148,7 +148,11 @@ extension MyPageVC { $0.image = ImageLiterals.icArrowRight } - containerView.addSubviews(icStar, label, icArrowRight) + containerView.addSubviews( + icStar, + label, + icArrowRight + ) icStar.snp.makeConstraints { $0.top.equalToSuperview().offset(22) @@ -297,9 +301,20 @@ extension MyPageVC { return } - view.addSubviews(myProfileView, myRunningProgressView, firstDivideView, - activityRecordInfoView, secondDivideView, goalRewardInfoView, - thirdDivideView, uploadedCourseInfoView, fourthDivideView, settingView, fifthDivideView, kakaoChannelAsk) + view.addSubviews( + myProfileView, + myRunningProgressView, + firstDivideView, + activityRecordInfoView, + secondDivideView, + goalRewardInfoView, + thirdDivideView, + uploadedCourseInfoView, + fourthDivideView, + settingView, + fifthDivideView, + kakaoChannelAsk + ) myProfileView.snp.makeConstraints { $0.top.equalTo(navibar.snp.bottom).offset(6) @@ -315,7 +330,11 @@ extension MyPageVC { } private func setMyProfileLayout() { - myProfileView.addSubviews(myProfileImage, myProfileNameLabel, myProfileEditButton) + myProfileView.addSubviews( + myProfileImage, + myProfileNameLabel, + myProfileEditButton + ) myProfileImage.snp.makeConstraints { $0.top.equalToSuperview().offset(11) @@ -344,8 +363,11 @@ extension MyPageVC { } private func setRunningProgressLayout() { - myRunningProgressView.addSubviews(myRunningLevelLabel, myRunningProgressBar, - myRunnigProgressPercentLabel) + myRunningProgressView.addSubviews( + myRunningLevelLabel, + myRunningProgressBar, + myRunnigProgressPercentLabel + ) myRunningLevelLabel.snp.makeConstraints { $0.top.equalToSuperview().offset(20) @@ -422,7 +444,11 @@ extension MyPageVC { } private func setVersionInfoLayout() { - view.addSubviews(topVersionDivideView, versionInfoView, bottomVersionDivideView) + view.addSubviews( + topVersionDivideView, + versionInfoView, + bottomVersionDivideView + ) topVersionDivideView.snp.makeConstraints { $0.top.equalTo(kakaoChannelAsk.snp.bottom) @@ -442,7 +468,10 @@ extension MyPageVC { $0.height.equalTo(4) } - versionInfoView.addSubviews(versionInfoLabel, versionInfoValueLabel) + versionInfoView.addSubviews( + versionInfoLabel, + versionInfoValueLabel + ) versionInfoLabel.snp.makeConstraints { $0.centerY.equalToSuperview() @@ -487,7 +516,7 @@ extension MyPageVC { self.showNetworkFailureToast() } case .failure(let error): - print(error.localizedDescription) + print("🍀🍀🍀\(error.localizedDescription)") self.showNetworkFailureToast() } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift index 0c7e7784..4d4dba8e 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift @@ -107,11 +107,6 @@ extension NicknameEditorVC { self.navigationController?.popViewController(animated: true) } - @objc private func didNicknameReturn() { - guard let nickname = nickNameTextField.text else { return } - self.updateUserNickname(nickname: nickname) - } - @objc private func textFieldTextDidChange() { guard let text = nickNameTextField.text else { return } @@ -123,8 +118,9 @@ extension NicknameEditorVC { } @objc private func finishEditNickname() { - didNicknameReturn() - self.navigationController?.popViewController(animated: false) + guard let nickname = nickNameTextField.text else { return } + + self.updateUserNickname(nickname: nickname) } } @@ -184,6 +180,13 @@ extension NicknameEditorVC: UITextFieldDelegate { extension NicknameEditorVC { func updateUserNickname(nickname: String) { + + guard nickname != self.currentNickname else { + print("💪 닉네임 변경 시도 전에 현재 닉네임과 동일한지 검사 성공 처리") + self.navigationController?.popViewController(animated: false) + return + } + LoadingIndicator.showLoading() userProvider.request(.updateUserNickname(nickname: nickname)) { [weak self] response in LoadingIndicator.hideLoading() @@ -193,15 +196,13 @@ extension NicknameEditorVC { let status = result.statusCode if 200..<300 ~= status { self.delegate?.nicknameEditDidSuccess() - self.dismiss(animated: false) - } - if status >= 400 { - print("400 error") + self.navigationController?.popViewController(animated: false) + } else { self.showNetworkFailureToast() } case .failure(let error): + self.showToast(message: "중복된 닉네임입니다.", heightOffset: 100) print(error.localizedDescription) - self.showNetworkFailureToast() } } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/NickNameSetUpVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/NickNameSetUpVC.swift index 76a01450..b887b23e 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/NickNameSetUpVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/NickNameSetUpVC.swift @@ -174,7 +174,7 @@ extension NickNameSetUpVC { } case .failure(let error): print(error.localizedDescription) - self.showNetworkFailureToast() + self.showToast(message: "중복된 닉네임입니다.", heightOffset: 100) } } } diff --git a/Runnect-iOS/fastlane/Fastfile b/Runnect-iOS/fastlane/Fastfile index 9bdfebf5..3ff4f9a5 100644 --- a/Runnect-iOS/fastlane/Fastfile +++ b/Runnect-iOS/fastlane/Fastfile @@ -21,66 +21,141 @@ platform :ios do ############# version ############# - desc "Set Marketing and Build version" - lane :set_version do |version| - increment_version_number( - version_number: version[:version], - xcodeproj: "./Runnect-iOS.xcodeproj" - ) - +desc "Set Marketing and Build version" +lane :set_version do |version| + increment_version_number( + version_number: version[:version], + xcodeproj: "./Runnect-iOS.xcodeproj" + ) + + increment_build_number( + build_number: Time.new.strftime("%Y.%m%d.%H%M"), # 2023.0703.2100 + xcodeproj: "./Runnect-iOS.xcodeproj" + ) +end + + + + + + ############# AppStore Release ############# + +desc "Build app and release to App Store." +lane :release do |version| + start_time = Time.now + version = version[:version] # fastlane release version:2.0.3 + + match( + type: "appstore", + app_identifier: "com.runnect.Runnect-iOS", + readonly: true + ) + + if version + puts "버전 정보: #{version}" + set_version(version: version) + else + puts "버전 입력 X" increment_build_number( - build_number: Time.new.strftime("%Y.%m%d.%H%M"), # 2023.0703.2100 + build_number: Time.new.strftime("%Y.%m%d.%H%M"), xcodeproj: "./Runnect-iOS.xcodeproj" ) end - - ############# beta ############# + build_app( + workspace: "Runnect-iOS.xcworkspace", + scheme: "Runnect-iOS", + configuration: "Release" + ) + + upload_to_app_store( + app_version: version, + submit_for_review: true, # 심사 제출은 자동(true)/수동(false)으로 진행 + force: true, + automatic_release: true, + skip_screenshots: true, + skip_metadata: false, # true면 메타데이터 업로드 건너뛰기 <릴리즈 사진이나 기타 등등 데이터> + submission_information: { add_id_info_uses_idfa: false } + ) + + # ✅ Slack 설정. + slack( + message: "🍎AppStore 심사까지 성공했습니다!🍎", + slack_url: ENV["RUNNECT_SLACK"], + payload: { "Version": version } + ) + + # ✅ 에러 처리. + error do |lane, exception, options| + slack( + message: "에러 발생 : #{exception}", + success: false, + slack_url: ENV["RUNNECT_SLACK"] + ) + end + end_time = Time.now + elapsed_time = ((end_time - start_time) / 60).round(2) + puts "fastlane.tools just completed your tasks in #{elapsed_time} minutes!🎉" +end - desc "Push a new beta build to TestFlight" - lane :beta do |version| - version = version[:version] - match( - type: "appstore", - app_identifier: "com.runnect.Runnect-iOS", - readonly: true + + + + + ############# beta ############# + +desc "Push a new beta build to TestFlight" +lane :beta do |version| + start_time = Time.now + version = version[:version] # 버전 정보 추출 + + match( + type: "appstore", + app_identifier: "com.runnect.Runnect-iOS", + readonly: true + ) + + if version + puts "버전 정보: #{version}" + set_version(version: version) + else + puts "버전 입력 X" + increment_build_number( + build_number: Time.new.strftime("%Y.%m%d.%H%M"), + xcodeproj: "./Runnect-iOS.xcodeproj" ) + end - if version - puts "버전 정보: #{version}" - set_version(version: version) - else - puts "버전 입력 X" - increment_build_number( - build_number: Time.new.strftime("%Y.%m%d.%H%M"), - xcodeproj: "./Runnect-iOS.xcodeproj" - ) - end + build_app(workspace: "Runnect-iOS.xcworkspace", scheme: "Runnect-iOS") - build_app(workspace: "Runnect-iOS.xcworkspace", scheme: "Runnect-iOS") + upload_to_testflight(skip_waiting_for_build_processing: true) - upload_to_testflight(skip_waiting_for_build_processing: true) + # ✅ Slack 설정. + slack( + username: "이명진", + message: "TestFlight 배포 성공.", + icon_url: "https://is1-ssl.mzstatic.com/image/thumb/Purple116/v4/a6/68/d0/a668d049-8c1a-0e7b-19c3-287093c0a501/AppIcon-1x_U007emarketing-0-7-0-85-220.png/1024x1024bb.png", + slack_url: ENV["RUNNECT_SLACK"], + payload: { "Version": version } + ) - # ✅ Slack 설정. + # ✅ 에러 처리. + error do |lane, exception, options| slack( - username: "이명진", - message: "TestFlight 배포 성공.", - icon_url: "https://is1-ssl.mzstatic.com/image/thumb/Purple116/v4/a6/68/d0/a668d049-8c1a-0e7b-19c3-287093c0a501/AppIcon-1x_U007emarketing-0-7-0-85-220.png/1024x1024bb.png", - slack_url: ENV["RUNNECT_SLACK"], - payload: { "Version": version } + message: "에러 발생 : #{exception}", + success: false, + slack_url: ENV["RUNNECT_SLACK"] ) - # ✅ 에러 처리. - error do |lane, exception, options| - slack( - message: "에러 발생 : #{exception}", - success: false, - slack_url: "https://hooks.slack.com/…" - ) - end end + end_time = Time.now + elapsed_time = ((end_time - start_time) / 60).round(2) + puts "fastlane.tools just completed your tasks in #{elapsed_time} minutes!🎉" +end + + ############# Device Management ############# @@ -112,4 +187,4 @@ platform :ios do readonly: true ) end -end +end \ No newline at end of file diff --git a/Runnect-iOS/fastlane/README.md b/Runnect-iOS/fastlane/README.md index 2b71c45e..24cda1e4 100644 --- a/Runnect-iOS/fastlane/README.md +++ b/Runnect-iOS/fastlane/README.md @@ -23,6 +23,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do Set Marketing and Build version +### ios release + +```sh +[bundle exec] fastlane ios release +``` + +Build app and release to App Store. + ### ios beta ```sh