The BrandButton app is a showcase project demonstrating a highly dynamic UI button component with an implementation of the Redux pattern for state management. This application is designed following SOLID principles, particularly focusing on the Open/Closed principle (O) and the Dependency Inversion Principle (D) to ensure extendability and maintainability.
screen.480p.mov
The application utilizes a Redux-like state management system to handle the button state dynamically. It abstracts concrete implementations into protocols, allowing flexibility and making the codebase ready for future extensions with minimal changes.
I've prioritized developing an extendable architecture based on SOLID principles, focusing on system robustness and flexibility. This approach means the current UI doesn't exactly match the Figma design. However, because of the strong architecture, adjusting the design later is straightforward, involving minor code adjustments.
- Dynamic UI update for a custom
BrandButton
component. - State managed by Redux-inspired
ButtonStore
. - Actions dispatched based on user interaction, reflecting immediate UI changes.
- Extensible action enumeration
ButtonAction
facilitating new features addition. - Decoupled View Controller logic via a
ButtonViewModel
.
- SwiftUI Compatibility: Designed to work effortlessly both with UIKit and SwiftUI
- Animation Transitions: Implemented smooth transition animations for state changes
- Easy Customization: The architecture of the app is thoughtfully designed with flexibility and customization in mind. Adjustments only require minimal updates to the reducer and the addition of new cases in the
ButtonAction
enum. While not every customization option has been fully implemented due to time constraints, the foundational work has been laid out to ensure that extending the button's capabilities is straightforward.
To see the BrandButton in action:
- Touch and hold the button.
- Drag outside the bounds of the button.
- Observe the various states being showcased sequentially.
Each state change will occur with a 2-second interval, demonstrating the dynamic styling capabilities and state management of the button in real-time.
The app's architecture incorporates Dependency Injection (DI), which abstracts renderers and state into protocols. This not only facilitates the change of button behavior but also enhances testability. The codebase is crafted to be unit-test friendly, allowing for efficient testing and QA processes.
class ButtonViewModel {
init(initialState: ButtonStateable,
primaryRenderer: BrandButtonRenderable,
secondaryRenderer: BrandButtonRenderable) {
// Implementation
}
}
The project includes snapshot tests to validate UI elements. These tests capture screenshots of UI states and compare them to reference images, helping to identify any visual changes. To execute the snapshot tests, use the keyboard shortcut Command + U
in Xcode. This will run all unit and snapshot tests in the project.
Adding new functionalities can be done by appending new cases to the ButtonAction enum. The compiler's type safety will prompt the necessary implementations wherever the action is processed.
The BrandButton component is fully compatible with SwiftUI, enabling dynamic UI interactions through gestures. The integration with SwiftUI is facilitated through the BrandButtonRepresentable
struct, which bridges the UIKit-based BrandButton with SwiftUI's declarative interface. This setup allows the button to react to state changes and user interactions in a SwiftUI environment.
To view the BrandButton
component within a SwiftUI context, simply modify the useSwiftUI
flag in the SceneDelegate
to true
.
let useSwiftUI = true
Within the BrandButtonView
, several gestures are configured to demonstrate the interactive and dynamic capabilities of the BrandButton:
Tap Gesture: A simple tap on the button toggles the highlight state, showcasing the button's response to basic user interaction.
Drag Gesture (Minimum Distance: 0): Initiates the button's highlight state to indicate a press-and-hold action.
Drag Gesture (Minimum Distance: 5): Changes the button's type (actionButton or successButton) based on the drag action's completion. This demonstrates the dynamic update of the button's appearance based on user interaction.
Drag Gesture (Minimum Distance: 400): Toggles the button's icon position between leading and trailing, or introduces an icon if none is set. This gesture showcases the ability to dynamically modify the button's icon configuration based on user actions.