home page jump over login feature in Swift - ios

I am pretty new to the swift, I am trying to implement the feature that when the user logged in, they will get directly to the home page rather than the login page every time they reopen the app.
I took the reference to the tutorial: https://www.youtube.com/watch?v=gjYAIXjpIS8&t=146s. and I implemented the is logged in boolean checking as he did, but I somehow encounter the trouble reopen the homepage while logged in. I have an error message:[Presentation] Attempt to present <UITabBarController: 0x7fa68102ea00> on <IFTTT.ViewController: 0x7fa67fe0c150> (from <IFTTT.ViewController: 0x7fa67fe0c150>) whose view is not in the window hierarchy.
This is how my login page controller class:(which is the entry point when opening the app) I tried present as the tutorial and performsegue and both shows up the same error message above
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if isLoggedIn() {
performSegue(withIdentifier: "logInJump", sender: nil)
}
}
fileprivate func isLoggedIn() -> Bool {
print("logged in status: \(UserDefaults.standard.bool(forKey: "isLoggedIn"))")
return UserDefaults.standard.bool(forKey: "isLoggedIn")
}
#IBAction func signInButton(_ sender: Any) {
print("sign in tapped")
if let appURL = URL(string: "http://vocation.cs.umd.edu/flask/register") {
UIApplication.shared.open(appURL) { success in
if success {
print("The URL was delivered successfully.")
} else {
print("The URL failed to open.")
}
}
} else {
print("Invalid URL specified.")
}
}
}
// button broder width
#IBDesignable extension UIButton {
#IBInspectable var borderWidth: CGFloat {
set {
layer.borderWidth = newValue
}
get {
return layer.borderWidth
}
}
#IBInspectable var cornerRadius: CGFloat {
set {
layer.cornerRadius = newValue
}
get {
return layer.cornerRadius
}
}
#IBInspectable var borderColor: UIColor? {
set {
guard let uiColor = newValue else { return }
layer.borderColor = uiColor.cgColor
}
get {
guard let color = layer.borderColor else { return nil }
return UIColor(cgColor: color)
}
}
}
and this is my messy story board and segues :
story board
I tried adding a navigation controller that the app entry point gets in there and it performs the isLoggedIn the same as the view controller class did, but it also has the same error.
Can someone walk me through how to fix it or any other better techniques? I felt like I am blind since I just get into the study of swift. Thank you!

You need to thread your request to perform the segue. Because your calling performSegue in the viewDidLoad() what happens is that your call is being called before everything is loaded, so you need to introduce some lag.
Threads are sometimes called lightweight processes because they have
their own stack but can access shared data. Because threads share the
same address space as the process and other threads within the
process, the operational cost of communication between the threads is
low, which is an advantage
An asynchronous function will await the execution of a promise, and an
asynchronous function will always return a promise. The promise
returned by an asynchronous function will resolve with whatever value
is returned by the function
Long story short, you need to wait for everything to be loaded into memory and if you're calling functions from the main stack/thread e.g. viewDidLoad() then there is a good chance that it hasn't been loaded into memory yet. Meaning, logInJump segue doesn't exist at that point in that view controller, thus your error.
The other possibility is you don't have the right view/segue ID but that should've thrown a different error.
Also, change the sender from nil to self. Actually this isn't necessary but I've always used self over nil
// change to desired number of seconds should be higher then 0
let when = DispatchTime.now() + 0.2
if isLoggedIn() {
DispatchQueue.main.asyncAfter(deadline: when) {
self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "logInJump", sender: nil)
}
}

Related

performSegue(withIdentifier) not being called

For some reason my segue is not being performed. Here is my storyboard setup.
This code is being called in viewDidAppear of my root view controller. In the XCode debugger it says that the fetch request returned 0 results before failing on the last line due to unexpectedly finding nil. If it returned 0 results, why wouldn't my segue be performed?
var fetchResult: [User] = []
do {
// Look for User entities in Core Data
fetchResult = try context.fetch(User.fetchRequest()) as! [User]
} catch {
fatalError("Fetch error")
}
if fetchResult.count == 0 {
// New user, calculate TDEE
performSegue(withIdentifier: "toTDEE", sender: self)
}
if fetchResult.count > 1 {
// ERROR: too many users
fatalError("fetch count > 1")
}
let user = fetchResult.first! as User
The call to performSegue(withIdentifier:,sender:) doesn't block the control flow. It's an asynchronous call that will make sure that UIKit eventually presents the new view controller. The code in your method will continue to execute, however.
As a result, it will encounter the last line let user = fetchResult.first! as User immediately afterwards. This will crash as fetchResult.first results is an optional and you force-unwrap it even though it is nil (the fetchResult is empty, after all). And this crash will happen even before UIKit has even started to present the new view controller.
As a general rule of thumb you should always use optional binding instead of force-unwrapping optionals.
There's excellent reading about this topic in this answer: https://stackoverflow.com/a/32170457/10165733. I recommend you have a look at it.
here is your updated RootViewController code:
//Changed back to this method
override func viewDidAppear(_ animated: Bool) {
setup()
}
func setup() {
var fetchResult: [User] = []
do {
// Look for User entities in Core Data
fetchResult = try context.fetch(User.fetchRequest()) as! [User]
} catch {
fatalError("Fetch error")
}
//Modified this with single if conditions
if fetchResult.count == 0 {
// New user, calculate TDEE
performSegue(withIdentifier: "toTDEE", sender: self)
} else if fetchResult.count > 1 {
// ERROR: Too many users
fatalError("fetch count > 1")
} else {
//if fetchResult.count == 1
if let user = fetchResult.first { // New Edit Here
if !user.didFinishSetup {
// Didn't finish setup, re-calculate TDEE
performSegue(withIdentifier: "toTDEE", sender: self)
}
}
}
}
And HERE is your updated project for more info.

In Cognito on iOS, handling new password required doesn't ever reach didCompleteNewPasswordStepWithError

I'm trying to implement functionality to respond to FORCE_CHANGE_PASSWORD on my iOS app that uses AWS Cognito. I used this Stack Overflow question which references this sample code. Right now, my code opens a view controller like it's supposed to; however, once on that view controller, I can't get it to do anything. In the sample code, it seems that when you want to submit the password change request you call .set on an instance of AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>, yet when I do this, the protocol function didCompleteNewPasswordStepWithError is never called. Interestingly, the other protocol function getNewPasswordDetails is called quickly after viewDidLoad and I can't tell why. I believe this shouldn't be called until the user has entered their new password, etc and should be in response to .set but I could be wrong.
My code is pretty identical to the sample code and that SO post, so I'm not sure what's going wrong here.
My relevant AppDelegate code is here:
extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startNewPasswordRequired() -> AWSCognitoIdentityNewPasswordRequired {
//assume we are presenting from login vc cuz where else would we be presenting that from
DispatchQueue.main.async {
let presentVC = UIApplication.shared.keyWindow?.visibleViewController
TransitionHelperFunctions.presentResetPasswordViewController(viewController: presentVC!)
print(1)
}
var vcToReturn: ResetPasswordViewController?
returnVC { (vc) in
vcToReturn = vc
print(2)
}
print(3)
return vcToReturn!
}
//put this into its own func so we can call it on main thread
func returnVC(completion: #escaping (ResetPasswordViewController) -> () ) {
DispatchQueue.main.sync {
let storyboard = UIStoryboard(name: "ResetPassword", bundle: nil)
let resetVC = storyboard.instantiateViewController(withIdentifier: "ResetPasswordViewController") as? ResetPasswordViewController
completion(resetVC!)
}
}
}
My relevant ResetPasswordViewController code is here:
class ResetPasswordViewController: UIViewController, UITextFieldDelegate {
#IBAction func resetButtonPressed(_ sender: Any) {
var userAttributes: [String:String] = [:]
userAttributes["given_name"] = firstNameField.text!
userAttributes["family_name"] = lastNameField.text!
let details = AWSCognitoIdentityNewPasswordRequiredDetails(proposedPassword: self.passwordTextField.text!, userAttributes: userAttributes)
self.newPasswordCompletion?.set(result: details)
}
}
extension ResetPasswordViewController: AWSCognitoIdentityNewPasswordRequired {
func getNewPasswordDetails(_ newPasswordRequiredInput: AWSCognitoIdentityNewPasswordRequiredInput, newPasswordRequiredCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>) {
self.newPasswordCompletion = newPasswordRequiredCompletionSource
}
func didCompleteNewPasswordStepWithError(_ error: Error?) {
DispatchQueue.main.async {
if let error = error as? NSError {
print("error")
print(error)
} else {
// Handle success, in my case simply dismiss the view controller
SCLAlertViewHelperFunctions.displaySuccessAlertView(timeoutValue: 5.0, title: "Success", subTitle: "You can now login with your new passowrd", colorStyle: Constants.UIntColors.emeraldColor, colorTextButton: Constants.UIntColors.whiteColor)
self.dismiss(animated: true, completion: nil)
}
}
}
}
Thank you so much for your help in advance and let me know if you need any more information.

AdMob - Get Notify when isReady property changes

I trying implement Rewarded Ad - Rewarded Ads New APIs (Beta). Video is load and isReady property is changing to true in a couple of seconds.
I have a button on which user press and Rewarded Video appear
This is function which is fire when user press on button
func presentRewardAd(from viewController: UIViewController) {
if rewardedAd.isReady {
rewardedAd.present(fromRootViewController: viewController, delegate: self)
}
}
The problem is
I want to hide button until video isReady == true, and when it's ready show button. So i want to get notify when rewardedAd.isReady is changing.
What i try so far:
class CustomRewardAd: GADRewardedAd {
private var observation: NSKeyValueObservation?
override init(adUnitID: String) {
super.init(adUnitID: adUnitID)
observation = observe(\.isReady, options: [.old, .new]) { object, change in
print("isReady changed from: \(change.oldValue!), updated to: \(change.newValue!)")
}
}
}
Also i tried this Using Key-Value Observing in Swift but same result.
But changeHandler never gets called. Am i doing something wrong?
Thanks!
I found solution, not ideal but it's works! Maybe this is can help someone in future.
When new rewarded request finishes, isReady property is set to true or false depends what response is.
private func createAndLoadRewardedAd() -> GADRewardedAd {
let rewardedAd = GADRewardedAd(adUnitID: "ca-app-pub-3940256099942544/1712485313")
rewardedAd.load(GADRequest()) { [weak self] error in
guard let self = self else { return }
self.videoIsReady?(rewardedAd.isReady) // already set
}
return rewardedAd
}
You are welcome!

iOS: Unit test with a void func in Swift

I want to test this method that doesn't return a value but I want to check if works fine.
Can you give me some suggestions?
func login() {
if Utility.feature.isAvailable(myFeat) {
if self.helper.ifAlreadyRed() {
self.showWebViewController()
} else {
let firstVC = FirstViewController()
self.setRootController(firstVC)
}
} else {
let secondVC = SecondViewController()
self.setRootController(secondVC)
}
}
so what's the best approach to apply unit test here?
Testing side effects is one approach. But for an example like the code in question, I actually prefer a subclass-and-expect approach.
Your code has three different paths.
If feature is available and already red, show web view controller.
If feature is available and not already red, show first view controller.
If feature is not available, show second view controller.
So assuming this login() function is part of FooViewController, one possibility is writing tests that follow this format:
func testLoginFeatureAvailableAndNotAlreadyRed() {
class TestVC: FooViewController {
let setRootExpectation: XCTExpectation
init(expectation: XCTExpectation) {
setRootExpectation = expectation
super.init()
}
override func setRootController(vc: UIViewController) {
defer { setRootExpectation.fulfill() }
XCTAssertTrue(vc is FirstViewController)
// TODO: Any other assertions on vc as appropriate
// Note the lack of calling super here.
// Calling super would inaccurately conflate our code coverage reports
// We're not actually asserting anything within the
// super implementation works as intended in this test
}
override func showWebViewController() {
XCTFail("Followed wrong path.")
}
}
let expectation = expectationWithDescription("Login present VC")
let testVC = TestVC(expectation: expectation)
testVC.loadView()
testVC.viewDidLoad()
// TODO: Set the state of testVC to whatever it should be
// to expect the path we set our mock class to expect
testVC.login()
waitForExpectationsWithTimeout(0, handler: nil)
}

How to update a textfield periodically?

I'm building an app which it communicates with socket periodically.
As long as the socket is open, then data will be transmitting from time to time.
However, my textFields(representing the data) do not update itself unless another view is introduces.
From the image above, initially my app will scans for the QR code as authentication method. Assuming authentication succeeded and the First view is loaded(the view after navigation controller).
The problem is it took quite some time to get the data.
Code that describe how I maneuver the connection.
override func viewDidLoad() {
super.viewDidLoad()
print("First view loaded")
//add observer
NSNotificationCenter.defaultCenter().addObserver(self, selector: "reachabilityStatusChanged", name: "ReachStatusChanged", object: nil)
reachabilityStatusChanged()
}
func reachabilityStatusChanged(){
switch reachabilityStatus{
case NOACCESS:
print("No access")
dispatch_async(dispatch_get_main_queue()){
print("No-access")
self.displayAlertMessage("No internet access, please try again later")
}
default:
dispatch_async(dispatch_get_main_queue()){
self.api.reportStatus()
self.api.importData()
socket.connect()
//data will be sent to text field at this point
}
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
print("First view appeared")
self.circleProgressView.progress = progressSliderValue
dispatch_async(dispatch_get_main_queue()){
self.distanceLabel.text = "\(appUserMileage)"
self.mileageLabel.text = "Target: \(appUserTarget) km"
}
}
From the code above as you can tell as the view is loaded(the view after navigation controller), it checks for network access. If network available then it will communicate with socket but by the time data gets in, the view is already loaded and appeared which means no data will be displayed at that time.
Is there a way to update the views periodically? I've done some research but all of them is about background fetching data which isn't suitable for my case.
I don't know where your appUserMileage and appUserTarget variables live, but how about something like this?
var appUserMileage: Int? {
didSet {
self.distanceLabel?.text = "\(appUserMileage ?? 0)" //? in case model calls this before outlets are loaded
}
}
var appUserTarget: Int? {
didSet {
self.mileageLabel?.text = "Target: \(appUserTarget ?? 0) km"
}
}

Resources