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