RXSwift: Tap Tap Sample App

|

In this tutorial, we’ll explore how to create a simple “Tap Tap” app using RxSwift, a powerful reactive programming framework for iOS.

By harnessing the power of RxSwift, we can manage asynchronous events in a clean and manageable way, making it perfect for building interactive apps.

We’ll walk through setting up a tap counter that responds in real-time, integrating RxSwift to handle taps, and creating a dynamic user experience.

Let’s dive in and see how RxSwift can simplify reactive event handling in iOS development!

Most important RXSwift commands used

Creating the Project

We chose to use UIKit for this project because it integrates more seamlessly with RxSwift, which is specifically designed to enhance reactive programming in UIKit-based applications. While SwiftUI has its own reactive framework (Combine), UIKit allows us to leverage RxSwift’s extensive operators and tools, making it ideal for managing asynchronous events and complex UI interactions in this project. This approach ensures we can build a responsive and interactive app efficiently, using RxSwift’s capabilities to their fullest within the UIKit environment.

To get started, it’s essential to import RxSwift and RxAlamofire, as these libraries provide the reactive tools needed for handling asynchronous tasks and network requests in a reactive style. You can add these libraries to your project using either Swift Package Manager (SPM) or CocoaPods. In this project, we chose to use Swift Package Manager for its simplicity and native integration with Xcode. With SPM, we can easily manage dependencies directly in Xcode, ensuring updates and compatibility are handled efficiently. This setup allows us to leverage RxSwift’s powerful operators and RxAlamofire’s reactive network handling seamlessly.

While using RxAlamofire isn’t strictly necessary, we included it in this project to demonstrate how reactive programming can simplify network requests. RxAlamofire builds on top of Alamofire, wrapping network calls in RxSwift observables, which allows us to handle responses in a reactive, chainable way. This integration makes it easy to manage asynchronous network tasks alongside other reactive elements in the app, providing a clear, streamlined approach for handling requests and responses as part of the reactive workflow.

Using Swift Package Manger import:
https://github.com/ReactiveX/RxSwift.git (5.2.0)
https://github.com/RxSwiftCommunity/RxAlamofire.git (5.1.0)

Then add it to your target:

Login Screen

The login screen was created specifically to demonstrate how RxAlamofire can be used for network requests in a reactive setup. By simulating a login process, we can show how RxAlamofire wraps network calls, allowing us to handle authentication requests reactively. This example highlights how RxAlamofire integrates seamlessly with RxSwift, making it easy to initiate, observe, and manage asynchronous login requests while keeping the code clean and responsive. This approach provides a practical example of handling real-time network communication in a reactive architecture.

First of all create the LoginViewController:

The key difference in this login screen is how RxSwift performs binding reactively, streamlining the flow of data between UI components and the ViewModel. In setupBindings(), RxSwift binds each UI element—such as loginTextField, passwordTextField, and rememberSwitch—directly to properties in the ViewModel, ensuring that changes are automatically synchronized. For example:

  • Text Fields: The username and password fields use reactive bindings that immediately update the ViewModel with any user input.
  • Switch and Button States: The state of rememberSwitch is bound to a ViewModel property, while the isLoginButtonEnabled property in the ViewModel is reactively bound to the loginButton‘s isEnabled state.
  • Login Button Action: When the loginButton is tapped, RxSwift initiates a login request using RxAlamofire, managing loading states and error handling reactively. Any errors automatically trigger a dialog, while successful login responses navigate to the home screen.
  • Guest Login: The guest login button reacts similarly, executing logic in the ViewModel and navigating when tapped.

This reactive binding with RxSwift not only simplifies the code by removing manual updating and monitoring but also makes it more responsive, as the UI dynamically reacts to changes in real time.

private func setupBindings() {
    loginTextField.rx.text.orEmpty
        .bind(to: viewModel.username)
        .disposed(by: disposeBag)
    
    passwordTextField.rx.text.orEmpty
        .bind(to: viewModel.password)
        .disposed(by: disposeBag)
    
    rememberSwitch.rx.isOn
        .bind(to: viewModel.rememberMe)
        .disposed(by: disposeBag)
    
    viewModel.isLoginButtonEnabled
        .bind(to: loginButton.rx.isEnabled)
        .disposed(by: disposeBag)
    
    loginButton.rx.tap
        .flatMapLatest { [unowned self] in
            showLoadingScreen()
            
            return self.viewModel.login()
                .catchError { error in
                    self.presentErrorDialog()
                    print("Login error: \(error.localizedDescription)")
                    return Observable.empty()
                }
        }
        .subscribe (onNext: { response in
            print("Response: \(response.token)")
            self.navigateToHome()
        }, onError: { error in
            self.presentErrorDialog()
            print("Error: \(error.localizedDescription)")
        })
        .disposed(by: disposeBag)
    
    guestButton.rx.tap
        .do { _ in
            print("taping guest")
            self.viewModel.loginAsGuest()
        }
        .subscribe { _ in
            self.navigateToHome()
        }
        .disposed(by: disposeBag)
}

NetworkService

In this project, the core logic for handling RxAlamofire requests is encapsulated in NetworkService.swift. This class manages network requests in a reactive, streamlined way, using RxAlamofire to simplify the flow of HTTP requests and responses.

The NetworkService class includes a private request method that handles requests of various HTTP methods, such as GET, POST, PUT, and DELETE, by defining each one as a public method (e.g., get, post). Here’s how it works:

  • Reactive HTTP Requests: Using RxAlamofire.requestData, each request is wrapped in an observable, which allows it to emit the response data or an error.
  • Response Handling: The request method checks the HTTP status code. If successful, the data is passed along the observable chain; otherwise, it emits an error with a message.
  • Decoding Data: The response data is decoded using JSONDecoder, mapping it to the expected data type (T: Decodable).
  • Error Handling with Default Values: If decoding fails, catchError provides default values for common types, ensuring the app remains responsive even if the request encounters an error.

This setup enables us to perform network requests in a clean, type-safe way while allowing other parts of the app to handle results reactively. With NetworkService, we can integrate RxAlamofire into the app’s reactive architecture, making asynchronous networking straightforward and consistent.

LoginViewModel

The LoginViewModel class handles the logic for managing the login process in a reactive way using RxSwift. This ViewModel encapsulates both the data-binding of user input fields and the logic for making network requests, allowing the LoginViewController to remain focused on UI tasks.

Here’s a breakdown of the main components:

  • Reactive Properties:
    • username, password, and rememberMe are BehaviorSubjects that hold the current values entered by the user and are used to track updates in real time.
    • isLoginButtonEnabled is an observable derived from username and password. It uses combineLatest to monitor changes in both fields and enables the login button only when both fields are non-empty.
  • Dependencies:
    • networkService is an instance of NetworkService, used here to handle network requests for login. It’s injected as a dependency, making it easy to mock for testing.
    • userDefaultData stores user data and the “Remember Me” preference in UserDefaults. It’s also injected, providing flexibility in data storage management.
  • login() Method:
    • This function performs the actual login request by calling networkService.post with the user’s email and password as parameters.
    • The method returns an Observable of LoginResponseDTO, which allows the ViewController to subscribe to the result and react accordingly.
    • Using .do(onNext:), the login response is saved to userDefaultData, along with the current value of rememberMe.
  • Other Functions:
    • hasRememberMeSave(): Checks if the “Remember Me” option is enabled and if there’s stored user data.
    • loginAsGuest(): Sets the “Remember Me” option for guest users.

This reactive approach keeps the login logic encapsulated in the LoginViewModel, making it easy to manage user input, perform network requests, and handle login states efficiently. The ViewModel ensures a clear separation of concerns, with each element of the login flow being reactive, clean, and testable.

HomeViewController

The HomeViewController class is a UITabBarController that serves as the main navigation hub of the app, featuring two tabs: Store and Lobby. It sets up and configures these tabs and interacts with the HomeViewModel to start certain reactive processes.

Key Components:

  • ViewModel:
    • HomeViewController initializes a HomeViewModel instance to handle data and business logic related to the home screen.
    • The call to viewModel.startTapFactory() in viewDidLoad() activates any reactive processes, such as incrementing taps, which may be managed by the ViewModel.
  • UI Setup (setupUI()):
    • Background: Sets a white background color for a clean appearance.
    • Tabs:
      • First Tab (Store): Configures a StoreViewController instance and assigns it a UITabBarItem with the title “Store” and an icon of a storefront. This tab serves as the main entry point for the store.
      • Second Tab (Lobby): Configures a LobbyViewController instance with a UITabBarItem that displays “Lobby” with a house icon.
      • Selected Tab: Sets the initial tab to be the “Lobby” (index 1), so it’s shown when the user first enters the HomeViewController.
  • Navigation Bar Visibility:
    • The viewWillAppear(_:) method hides the navigation bar, creating a full-screen experience and keeping the focus on the tab bar and the content of each tab.

Purpose:

This HomeViewController serves as a centralized UI controller, simplifying navigation by providing quick access to the Store and Lobby views. The integration with HomeViewModel allows it to manage reactive processes, keeping logic separate and ensuring that the UI remains responsive and easy to navigate.


LobbyViewController

The LobbyViewController is a simple, interactive screen that encourages user engagement by prompting the user to tap a specific area. This screen is built using UIKit and RxSwift to manage user interactions reactively.

Key Components:

  • UI Elements:
    • statusBarView: A custom view that likely displays status-related information and sits at the top of the screen.
    • tapHereLabel: A label prompting the user to “Tap me!” in bold, large text, making it clear that the view is interactive.
    • tapView: A bordered UIView that serves as the tappable area, visually separating it as an area to interact with.
  • UI Layout (setupUI()):
    • The screen has a white background, with statusBarView at the top.
    • tapView fills most of the screen below the statusBarView, with tapHereLabel centered within it.
    • The layout is set up with constraints to ensure responsive positioning, adapting to different screen sizes.
  • Reactive Binding with RxSwift (setupBindings()):
    • A UITapGestureRecognizer is added to tapView, allowing taps to trigger reactive events.
    • Using tapGesture.rx.event, the gesture is bound to a reactive chain that, on each tap, calls TapCountManager.shared.incrementTapCount() to increase the tap count.
    • The RxSwift disposeBag manages the subscription, ensuring memory is handled correctly.

Purpose:

The LobbyViewController provides an interactive experience for users, encouraging taps and increasing a global tap count managed by TapCountManager. By utilizing RxSwift, the view controller remains clean and reactive, responding instantly to user interaction and updating the tap count efficiently. This setup exemplifies how RxSwift can be used to handle UI gestures in a reactive, declarative manner, keeping the UI responsive and concise.


StatusBarView

The StatusBarView is a custom UIView component that displays the current tap count in real-time. It’s designed to provide feedback on the user’s progress by showing the number of taps dynamically, using RxSwift for reactive updates.

Key Components:

  • tapCountManager Dependency:
    • tapCountManager is an instance of TapCountManagerProtocol (using TapCountManager.shared by default) and provides access to the current tap count. This is where the view retrieves the latest tap count, making the StatusBarView responsive to changes in the global tap count.
  • UI Element (tapLabelScore):
    • tapLabelScore is a UILabel centered within StatusBarView, styled with medium font and centered alignment to clearly display the current number of taps.
  • Initialization:
    • The StatusBarView has two initializers: init(frame:) and init(coder:), both calling setupView() to configure the view’s appearance and layout.
  • UI Setup (setupView()):
    • The background color is set to a light gray (systemGray5) to make the status bar visually distinct.
    • tapLabelScore is added to the center of the view using Auto Layout constraints, ensuring it’s centrally aligned.
  • Reactive Binding (setupBindings()):
    • The setupBindings() method binds tapCountManager.tapCount to tapLabelScore.rx.text:
      • tapCountManager.tapCount emits the latest tap count as an observable.
      • The .map { "Taps: \($0)" } operator formats the tap count to display as "Taps: [count]".
      • .bind(to: tapLabelScore.rx.text) updates the label’s text reactively.
      • The subscription is disposed of in disposeBag to manage memory automatically.

Purpose:

The StatusBarView provides a clear and responsive display of the user’s tap count. By using RxSwift for reactive data binding, it automatically updates the tap count without requiring additional code in view controllers. This setup illustrates how custom views can integrate RxSwift for real-time updates, keeping the UI responsive and cleanly separated from business logic.

StoreViewController

The StoreViewController is a UIViewController that displays the in-game store where players can purchase items using taps, with each item displayed in a table view. This controller is built with RxSwift, allowing it to react to real-time changes in the tap count and trigger actions based on certain conditions, such as when the player wins the game.

Key Components:

  • ViewModel and RxSwift Integration:
    • StoreViewController relies on StoreViewModel for managing data and business logic, making the controller primarily focused on UI tasks.
    • disposeBag handles the disposal of subscriptions to RxSwift observables, preventing memory leaks.
  • UI Elements (setupUI):
    • statusBarView displays a summary of the player’s progress or status and is anchored to the top of the view.
    • tableView is used to list store items, each represented by a row, and fills the rest of the screen below statusBarView.
  • Bindings (setupBindings):
    • Tap Count Binding: Observes the tapCount from viewModel, reloading the table whenever the count changes, so the UI remains updated based on the player’s available taps.
    • Win Dialog: Observes viewModel.showWinDialog, triggering presentWinDialog() when the player reaches the winning conditions.
  • Present Win Dialog (presentWinDialog):
    • Displays an alert congratulating the player when they win the game. This function is triggered by the showWinDialog observable from the ViewModel.
  • TableView DataSource:
    • numberOfRowsInSection: Returns the number of items in the viewModel.items array.
    • willSelectRowAt: Enables row selection only if the player has enough taps to buy the item.
    • cellForRowAt: Configures each cell to display item details (icon, name, and price) and adjusts the cell’s interactivity and opacity based on the player’s ability to buy it.
  • TableView Delegate (didSelectRowAt):
    • Handles the purchase action when an item is selected by calling viewModel.purchase(item:).
    • After a purchase, the table view is reloaded to update item states and ensure accurate UI representation.

Purpose:

StoreViewController provides a responsive in-game store experience where players can purchase upgrades using taps. Through RxSwift bindings, it stays updated with the latest tap count and dynamically adjusts item accessibility and interactivity. The presentWinDialog offers feedback when the player achieves the game’s win conditions, creating a seamless, reactive user experience.

Visit the repository to get the source code

https://github.com/san0suke/rxswift-example

Leave a Reply

Your email address will not be published. Required fields are marked *