Skip to content

Commit fd82fe5

Browse files
Merge pull request #547 from PermanentOrg/bugfix/VSP-1704-iOS-Fix-resubmission-issue-for-appstore-rejection
VSP-1704 iOS Fix resubmission issue for appstore rejection
2 parents 8b0cc77 + 16c7153 commit fd82fe5

File tree

6 files changed

+189
-102
lines changed

6 files changed

+189
-102
lines changed

.github/workflows/deployRelease.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,48 @@ on:
66
branches: [ "Release-**" ]
77

88
jobs:
9+
test:
10+
name: Run Unit Tests
11+
runs-on: macos-26
12+
environment: production
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v3
17+
- name: Update Bundler
18+
uses: ruby/setup-ruby@v1
19+
with:
20+
ruby-version: '3.1.0'
21+
bundler-cache: true
22+
- uses: maxim-lobanov/setup-xcode@v1
23+
with:
24+
xcode-version: '26.2'
25+
- name: Cache CocoaPods
26+
uses: actions/cache@v3
27+
with:
28+
path: Pods
29+
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
30+
restore-keys: |
31+
${{ runner.os }}-pods-
32+
- name: Run Unit Tests
33+
env:
34+
CLIENT_ID: ${{ secrets.IOS_FIREBASE_CLIENT_ID }}
35+
REVERSED_CLIENT_ID: ${{ secrets.IOS_FIREBASE_REVERSED_CLIENT_ID }}
36+
API_KEY: ${{ secrets.IOS_FIREBASE_API_KEY }}
37+
GCM_SENDER_ID: ${{ secrets.IOS_FIREBASE_GCM_SENDER_ID }}
38+
PROJECT_ID: ${{ secrets.IOS_FIREBASE_PROJECT_ID }}
39+
STORAGE_BUCKET: ${{ secrets.IOS_FIREBASE_STORAGE_BUCKET }}
40+
GOOGLE_APP_ID: ${{ secrets.IOS_FIREBASE_GOOGLE_APP_ID }}
41+
AUTH_TENANT_ID: ${{ secrets.MOBILE_PROD_AUTH_TENANT_ID }}
42+
AUTH_CLIENT_ID: ${{ secrets.MOBILE_PROD_AUTH_CLIENT_ID }}
43+
AUTH_CLIENT_SECRET: ${{ secrets.MOBILE_PROD_AUTH_CLIENT_SECRET }}
44+
STRIPE_PUB_KEY: ${{ secrets.MOBILE_PROD_STRIPE_PUB_KEY }}
45+
SKIPLINT: true
46+
TEST_PLAN: 'unit-tests'
47+
run: bundle exec fastlane tests
48+
949
build:
50+
needs: test
1051
name: Build and deploy a release build to TestFlight
1152
runs-on: macos-26
1253
environment: production
@@ -22,6 +63,13 @@ jobs:
2263
- uses: maxim-lobanov/setup-xcode@v1
2364
with:
2465
xcode-version: '26.2'
66+
- name: Cache CocoaPods
67+
uses: actions/cache@v3
68+
with:
69+
path: Pods
70+
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
71+
restore-keys: |
72+
${{ runner.os }}-pods-
2573
- name: Run Fastlane AppStore
2674
env:
2775
CLIENT_ID: ${{ secrets.IOS_FIREBASE_CLIENT_ID }}

Permanent.xcodeproj/project.pbxproj

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4850,14 +4850,10 @@
48504850
inputFileListPaths = (
48514851
"${PODS_ROOT}/Target Support Files/Pods-Permanent/Pods-Permanent-resources-${CONFIGURATION}-input-files.xcfilelist",
48524852
);
4853-
inputPaths = (
4854-
);
48554853
name = "[CP] Copy Pods Resources";
48564854
outputFileListPaths = (
48574855
"${PODS_ROOT}/Target Support Files/Pods-Permanent/Pods-Permanent-resources-${CONFIGURATION}-output-files.xcfilelist",
48584856
);
4859-
outputPaths = (
4860-
);
48614857
runOnlyForDeploymentPostprocessing = 0;
48624858
shellPath = /bin/sh;
48634859
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Permanent/Pods-Permanent-resources.sh\"\n";
@@ -4871,14 +4867,10 @@
48714867
inputFileListPaths = (
48724868
"${PODS_ROOT}/Target Support Files/Pods-PermanentTests/Pods-PermanentTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
48734869
);
4874-
inputPaths = (
4875-
);
48764870
name = "[CP] Embed Pods Frameworks";
48774871
outputFileListPaths = (
48784872
"${PODS_ROOT}/Target Support Files/Pods-PermanentTests/Pods-PermanentTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
48794873
);
4880-
outputPaths = (
4881-
);
48824874
runOnlyForDeploymentPostprocessing = 0;
48834875
shellPath = /bin/sh;
48844876
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PermanentTests/Pods-PermanentTests-frameworks.sh\"\n";
@@ -4936,14 +4928,10 @@
49364928
inputFileListPaths = (
49374929
"${PODS_ROOT}/Target Support Files/Pods-Permanent/Pods-Permanent-frameworks-${CONFIGURATION}-input-files.xcfilelist",
49384930
);
4939-
inputPaths = (
4940-
);
49414931
name = "[CP] Embed Pods Frameworks";
49424932
outputFileListPaths = (
49434933
"${PODS_ROOT}/Target Support Files/Pods-Permanent/Pods-Permanent-frameworks-${CONFIGURATION}-output-files.xcfilelist",
49444934
);
4945-
outputPaths = (
4946-
);
49474935
runOnlyForDeploymentPostprocessing = 0;
49484936
shellPath = /bin/sh;
49494937
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Permanent/Pods-Permanent-frameworks.sh\"\n";

Permanent/Common/Base/SwiftUINavigationViews/CustomNavigationView.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,13 @@ struct CustomNavigationView<Content: View, LeftButton: View, RightButton: View>:
8484
.navigationViewStyle(StackNavigationViewStyle())
8585
}
8686
.onAppear {
87-
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
87+
if #available(iOS 16.0, *) {
88+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
89+
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))
90+
}
91+
} else {
92+
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
93+
}
8894
#if !canImport(ShareExtension)
8995
AppDelegate.orientationLock = .portrait
9096
ScrollViewAppearanceManager.shared.pushScrollViewBounce(enabled: false, identifier: "CustomNavigationView")

Permanent/Common/Base/SwiftUINavigationViews/SimpleCustomNavigationView.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@ struct SimpleCustomNavigationView<Content: View, LeftButton: View, RightButton:
7070
.navigationViewStyle(StackNavigationViewStyle())
7171
}
7272
.onAppear {
73-
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
73+
if #available(iOS 16.0, *) {
74+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
75+
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))
76+
}
77+
} else {
78+
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
79+
}
7480
#if !canImport(ShareExtension)
7581
AppDelegate.orientationLock = .portrait
7682
ScrollViewAppearanceManager.shared.pushScrollViewBounce(enabled: false, identifier: "SimpleCustomNavigationView")

Permanent/Modules/AccountSettings/ViewController/AccountInfoViewController.swift

Lines changed: 80 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,53 @@ class AccountInfoViewController: BaseViewController<InfoViewModel> {
2929
viewModel?.trackEvents(action: AccountEventAction.openLoginInfo)
3030
}
3131

32+
override func viewWillAppear(_ animated: Bool) {
33+
super.viewWillAppear(animated)
34+
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
35+
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
36+
}
37+
38+
override func viewWillDisappear(_ animated: Bool) {
39+
super.viewWillDisappear(animated)
40+
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
41+
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
42+
}
43+
44+
@objc private func keyboardWillShow(_ notification: Notification) {
45+
guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
46+
let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
47+
let inset = keyboardFrame.height - view.safeAreaInsets.bottom
48+
UIView.animate(withDuration: duration) {
49+
self.scrollView.contentInset.bottom = inset
50+
self.scrollView.verticalScrollIndicatorInsets.bottom = inset
51+
}
52+
}
53+
54+
@objc private func keyboardWillHide(_ notification: Notification) {
55+
guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
56+
UIView.animate(withDuration: duration) {
57+
self.scrollView.contentInset.bottom = 0
58+
self.scrollView.verticalScrollIndicatorInsets.bottom = 0
59+
}
60+
}
61+
62+
@objc private func moveToNextFromPhone() {
63+
addressView.textField?.becomeFirstResponder()
64+
}
65+
3266
private func initUI() {
3367
title = .accountInfo
3468
view.backgroundColor = .white
3569

36-
accountNameView.configureElementUI(label: .accountName, returnKey: UIReturnKeyType.done)
37-
primaryEmailView.configureElementUI(label: .primaryEmail, returnKey: UIReturnKeyType.done)
38-
mobilePhoneView.configureElementUI(label: .mobilePhone, returnKey: UIReturnKeyType.done, keyboardType: .numbersAndPunctuation)
39-
addressView.configureElementUI(label: "Address Line 1".localized(), returnKey: UIReturnKeyType.done)
40-
addressView2.configureElementUI(label: "Address Line 2".localized(), returnKey: UIReturnKeyType.done)
41-
cityView.configureElementUI(label: .city, returnKey: UIReturnKeyType.done)
42-
stateView.configureElementUI(label: .stateOrRegion, returnKey: UIReturnKeyType.done)
43-
postalCodeView.configureElementUI(label: .postalcode, returnKey: UIReturnKeyType.done)
44-
countryView.configureElementUI(label: .country, returnKey: UIReturnKeyType.done)
70+
accountNameView.configureElementUI(label: .accountName, returnKey: .next)
71+
primaryEmailView.configureElementUI(label: .primaryEmail, returnKey: .next)
72+
mobilePhoneView.configureElementUI(label: .mobilePhone, returnKey: .next, keyboardType: .phonePad)
73+
addressView.configureElementUI(label: "Address Line 1".localized(), returnKey: .next)
74+
addressView2.configureElementUI(label: "Address Line 2".localized(), returnKey: .next)
75+
cityView.configureElementUI(label: .city, returnKey: .next)
76+
stateView.configureElementUI(label: .stateOrRegion, returnKey: .next)
77+
postalCodeView.configureElementUI(label: .postalcode, returnKey: .next)
78+
countryView.configureElementUI(label: .country, returnKey: .done)
4579
contentUpdateButton.configureActionButtonUI(title: .save)
4680
deleteAccountButton.configureActionButtonUI(title: "Delete Account".localized(), bgColor: .deepRed)
4781

@@ -54,10 +88,27 @@ class AccountInfoViewController: BaseViewController<InfoViewModel> {
5488
stateView.delegate = self
5589
postalCodeView.delegate = self
5690
countryView.delegate = self
57-
91+
92+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
93+
tapGesture.cancelsTouchesInView = false
94+
scrollView.addGestureRecognizer(tapGesture)
95+
5896
getUserDetails()
97+
98+
99+
//toolbar for having a next button on phone number introduction
100+
let toolbar = UIToolbar()
101+
toolbar.sizeToFit()
102+
let nextButton = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(moveToNextFromPhone))
103+
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
104+
toolbar.items = [spacer, nextButton]
105+
mobilePhoneView.textField?.inputAccessoryView = toolbar
59106
}
60107

108+
@objc private func dismissKeyboard() {
109+
view.endEditing(true)
110+
}
111+
61112
@IBAction func pressedUpdateButton(_ sender: RoundedButton) {
62113
attemptValuesChange()
63114
}
@@ -147,41 +198,32 @@ extension AccountInfoViewController: UITextFieldDelegate {
147198
}
148199

149200
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
150-
textField.resignFirstResponder()
151-
152-
switch textField {
153-
case postalCodeView.textField, countryView.textField, cityView.textField, stateView.textField:
154-
let point = CGPoint.zero
155-
scrollView.setContentOffset(point, animated: true)
156-
157-
default:
158-
break
201+
let fields = [
202+
accountNameView.textField,
203+
primaryEmailView.textField,
204+
mobilePhoneView.textField,
205+
addressView.textField,
206+
addressView2.textField,
207+
cityView.textField,
208+
stateView.textField,
209+
postalCodeView.textField,
210+
countryView.textField
211+
]
212+
if let index = fields.firstIndex(of: textField), index < fields.count - 1 {
213+
fields[index + 1]?.becomeFirstResponder()
214+
} else {
215+
textField.resignFirstResponder()
159216
}
160217
return true
161218
}
162219

163220
func textFieldDidBeginEditing(_ textField: UITextField) {
164-
let changePosition: [CGFloat] = [view.frame.height / 10, view.frame.height / 7]
165221
(textField as? PETextField)?.toggleBorder(active: true)
166-
167-
let point: CGPoint
168-
switch textField {
169-
case cityView.textField:
170-
point = CGPoint(x: 0, y: textField.frame.origin.y + changePosition[0])
171-
172-
case stateView.textField:
173-
point = CGPoint(x: 0, y: textField.frame.origin.y + changePosition[0])
174-
175-
case postalCodeView.textField:
176-
point = CGPoint(x: 0, y: textField.frame.origin.y + changePosition[1])
177-
178-
case countryView.textField:
179-
point = CGPoint(x: 0, y: textField.frame.origin.y + changePosition[1])
180-
181-
default:
182-
point = CGPoint.zero
222+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
223+
let fieldFrame = self.scrollView.convert(textField.bounds, from: textField)
224+
let visibleRect = fieldFrame.insetBy(dx: 0, dy: -16)
225+
self.scrollView.scrollRectToVisible(visibleRect, animated: true)
183226
}
184-
scrollView.setContentOffset(point, animated: true)
185227
}
186228

187229
func textFieldDidEndEditing(_ textField: UITextField) {

0 commit comments

Comments
 (0)