For this series, we are developing an iOS game called “TapMe”. As TapMe is a multiplayer game, it provides registration for the new users and login for the existing ones. In this article, we are going to demonstrate how to handle user registration and login, as well as how to store a player’s information in your Backendless database.
The source code for the game is available in the author’s personal Github repo: https://github.com/olgadanylova/TapMe.git
You can read Part 1 of this series here.
First, we need to insert the existing APP_ID and API_KEY values into the AppDelegate.m class; otherwise, when the first-time user tries to sign up or log in, he or she will receive the “KEY before running the app. Closed” error and the app will close.
AlertViewController.swift
func showErrorAlertWithExit(_ target: UIViewController) { let alert = UIAlertController(title: "❗️Error", message: "Make sure to configure the app with your APP ID and API KEY before running the app. \nApplication will be closed", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { action in exit(0) })) target.view.endEditing(true) target.present(alert, animated: true) }
The registration screen appears after pressing the “Sign up” button on the main screen. The user can set his/her profile picture, name, email address, and password.
After pressing the “Sign up” button, registration is complete and the user will be automatically signed in to start a game.
During the registration process, a new player based on the BackendlessUser is created. The profile picture is uploaded using the File Service API to the Backendless server and is stored in the tapMeProfileImages folder.
We will use the snippets of code below to complete the new player registration process:
Player.swift
@objcMembers class Player: NSObject { var objectId: String? var profileImageUrl: String? var name: String? var user: BackendlessUser? var maxScore: NSInteger = 0 }
The next section of code describes:
RegisterViewController.swift
let data = UIImagePNGRepresentation(cropToBounds(image: profileImageView.image!, width: 256, height: 256) ) let filePathName = String(format: "/tapMeProfileImages/%@.png", emailField.text!) let emailFieldText = self.emailField.text as NSString? let passwordFieldText = self.passwordField.text as NSString? // 1) Backendless.sharedInstance().file.uploadFile(filePathName, content: data, overwriteIfExist: true, response: { profileImage in let queryBuilder = DataQueryBuilder()! queryBuilder.setWhereClause((String(format: "email = '%@'", emailFieldText!))) // 2) Backendless.sharedInstance().data.of(BackendlessUser.ofClass()).find(queryBuilder, response: { registeredUsers in if registeredUsers?.first != nil { // 3) if BackendlessUser already exists self.createNewPlayer(registeredUsers?.first as? BackendlessUser, profileImage, color) } else { // 2-3) if BackendlessUser doesn't exist let user = BackendlessUser() user.email = emailFieldText user.password = passwordFieldText! Backendless.sharedInstance().userService.register(user, response: { registeredUser in self.createNewPlayer(registeredUser, profileImage, color) }, error: { fault in AlertViewController.sharedInstance.showErrorAlert(fault!, self) self.returnToSignUp(color!) }) } }, error: { fault in AlertViewController.sharedInstance.showErrorAlert(fault!, self) }) }, error: { fault in AlertViewController.sharedInstance.showErrorAlert(fault!, self) self.returnToSignUp(color!) })
func createNewPlayer(_ registeredUser: BackendlessUser?, _ profileImage: BackendlessFile?, _ color: UIColor?) { let newPlayer = Player() newPlayer.profileImageUrl = profileImage?.fileURL newPlayer.maxScore = 0 newPlayer.name = self.nameField.text Backendless.sharedInstance().data.of(Player.ofClass()).save(newPlayer, response: { player in let userId: String = registeredUser!.objectId! as String Backendless.sharedInstance().data.of(Player.ofClass()).setRelation("user:Users:1", parentObjectId: (player as! Player).objectId, childObjects: [userId], response: { relationSet in DispatchQueue.main.async { self.profileImageView.image = UIImage(named: "profileImage.png") self.activityIndicator.stopAnimating() self.activityIndicator.isHidden = true self.performSegue(withIdentifier: "unwindToLoginVC", sender: nil) self.nameField.text = "" self.emailField.text = "" self.passwordField.text = "" self.navigationController?.navigationBar.isUserInteractionEnabled = true self.navigationController?.navigationBar.tintColor = color } }, error: { fault in AlertViewController.sharedInstance.showErrorAlert(fault!, self) self.returnToSignUp(color!) }) }, error: { fault in AlertViewController.sharedInstance.showErrorAlert(fault!, self) self.returnToSignUp(color!) }) }
AlertViewController.swift
func showErrorAlert(_ fault: Fault, _ target: UIViewController) { let alert = UIAlertController(title: "❗️Error", message: fault.detail, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)) target.view.endEditing(true) target.present(alert, animated: true) }
Some errors could appear while registering a new player. If you encounter any of these errors, you can look up solutions here: https://backendless.com/docs/ios/doc.html#user-registration
The player should enter his/her login and password to sign in.
Some errors could appear while signing in. You can see more detail about those errors here: https://backendless.com/docs/ios/users_login.html#errors
Our “TapMe” app is designed to remember the logged-in player and login will occur automatically after the app restart. If the player signs in on another device, the first device will be logged out and show the login screen when the app restarts.
LoginViewController.swift
private var timer: Timer? override func viewDidLoad() { super.viewDidLoad() guard Backendless.sharedInstance().userService.currentUser == nil else { if Backendless.sharedInstance().userService.isValidUserToken() { timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(startGame), userInfo: nil, repeats: false) } else { Backendless.sharedInstance().userService.logout({ }, error: { fault in if fault?.faultCode == "404" { AlertViewController.sharedInstance.showErrorAlertWithExit(self) } }) } return } }
@IBAction func startGame() { self.performSegue(withIdentifier: "segueToTapMe", sender: nil) }
These are the steps of the login method:
func login(_ userEmail: String, _ userPassword: String) { let queryBuilder = DataQueryBuilder()! queryBuilder.setWhereClause(String(format: "user.email = '%@'", userEmail)) // 1 Backendless.sharedInstance().data.of(Player.ofClass()).find(queryBuilder, response: { foundPlayers in // 2 if foundPlayers?.first != nil { Backendless.sharedInstance().userService.setStayLoggedIn(true) Backendless.sharedInstance().userService.login(userEmail, password: userPassword, response: { loggedInUser in self.startGame() }, error: { fault in if fault?.faultCode == "3087" { AlertViewController.sharedInstance.showErrorAlert(Fault(message: fault?.message, detail: "Please confirm your email address so you can login"), self) } else { AlertViewController.sharedInstance.showErrorAlert(fault!, self) } }) } else { AlertViewController.sharedInstance.showRegisterPlayerAlert(self) } }, error: { fault in if fault?.faultCode == "404" { AlertViewController.sharedInstance.showErrorAlertWithExit(self) } else { AlertViewController.sharedInstance.showErrorAlert(fault!, self) } }) }
AlertViewController.swift
func showRegisterPlayerAlert(_ target: UIViewController) { let alert = UIAlertController(title: "???? Create a new player", message: "Please sign up as a new player", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) target.view.endEditing(true) target.present(alert, animated: true) }
That’s all for now. Next time, I’ll show you how to calculate and display the players on a leaderboard.
Happy building!