The logic of the development of mobile applications is the gradual complication of the functional load on the user interface.
This, in turn, leads to the growth of the code base and the difficulty of maintaining it.
MFS
- allows you to create a modern design of applications and at the same time avoid such a phenomenon as MassiveViewController
.
Photo: 10 years of the App Store: The design evolution of the earliest apps - 9to5Mac
Reasons for creating a pattern
viewDidLoad
.
layout
.
, .
, SwiftUI
.
, , , iOS 13
.
- , .
MFS
- .
.
MFS
(Managment
-Frames
-Styles
) , .
MFS ?
, , .
, Storyboard
Autolayout
.
Autolayout
, MFS
- frame
, - .
, Autolayout
.
, Autolayout
, , , constraints
.
MFS
, MassiveViewController
.
|
|
---|---|
|
, , , . |
|
|
|
|
, +Managment
, , .
, pure function.
"" , .
, , , "" .
, , +Frames
.
+Styles
, .
(ViewController.m
) (.:UITableViewDelegate
, UITableViewDataSource
), IBAction
.
, , - .
, - subviews
.
, , ~300 ObjC
- Swift
.
, " " .
, , layout
, .
UI
UI
UIViewController
.
UI
viewDidAppear
, prepareUI
.
, , .
, , , - .
, , resizeSubviews
, , , , updateStyles
bindDataFrom
.
.
.
, , .
Autolayout
.
.h
/.m
.
, , , , .
@interface LoginController : UIViewController
// ViewModel
@property (nonatomic, strong, nullable) LoginViewModel* viewModel;
@property (nonatomic, strong, nullable) LoginViewModel* oldViewModel;
// UI
@property (nonatomic, strong, nullable) UIImageView* logoImgView;
@property (nonatomic, strong, nullable) UIButton* signInButton;
@property (nonatomic, strong, nullable) UIButton* signUpButton;
@property (nonatomic, strong, nullable) CAGradientLayer *gradient;
@property (nonatomic, assign) CGSize oldSize;
#pragma mark - Actions
- (void) signUpBtnAction:(UIButton*)sender;
- (void) signInBtnAction:(UIButton*)sender;
#pragma mark - Initialization
+ (LoginController*) initWithViewModel:(nullable LoginViewModel*)viewModel;
@end
oldViewModel
oldSize
- .
.
@interface LoginController ()
@end
@implementation LoginController
#pragma mark - Life cycle
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self prepareUI];
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
__weak LoginController* weak = self;
[coordinator animateAlongsideTransition:nil
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[weak resizeSubviews:weak.viewModel];
} completion:nil];
}];
}
#pragma mark - Action
- (void) signUpBtnAction:(UIButton*)sender{
[self.viewModel signUpBtnAction];
}
- (void) signInBtnAction:(UIButton*)sender{
[self.viewModel signInBtnAction];
}
#pragma mark - Getters/Setters
- (void)setViewModel:(LoginViewModel *)viewModel
{
_viewModel = viewModel;
if ((!self.oldViewModel) && (self.view)){
[self prepareUI];
} else if ((self.oldViewModel) && (self.view)){
[self bindDataFrom:viewModel];
[self resizeSubviews:viewModel];
}
}
#pragma mark - Initialization
+ (LoginController*) initWithViewModel:(nullable LoginViewModel*)viewModel
{
LoginController* vc = [[LoginController alloc] init];
if (vc) {
vc.viewModel = (viewModel) ? viewModel : [LoginViewModel defaultMockup];
}
return vc;
}
@end
, , .
+Managment
|
|
|
---|---|---|
|
❌ |
|
|
❌ |
|
|
✅ |
|
|
✅ |
|
|
✅ |
|
|
✅ |
|
|
❌ |
|
|
❌ |
|
, UI
:
subviews
UI
.
( ).
subviews
.
subviews
(/ ).
subviews
.
frames
subviews
.
subviews
.
+Managment
viewDidAppear
, - prepareUI
, .
, resizeSubviews
bindDataFrom
, .
, , , , UIImageView
, , 0x0
, .
subviews
, .
/*----------------------------------------------------------------------
.
----------------------------------------------------------------------*/
- (void) prepareUI
{
if (self.view){
[self removeSubviews];
[self initSubviews:self.viewModel];
[self updateStyles:self.viewModel];
[self bindDataFrom:self.viewModel];
[self resizeSubviews:self.viewModel];
[self addSubviewsToSuperView];
[self postUIsetting];
}
}
subviews
.
- .
, viewModel
subviews
viewModel
, subviews
, , , subviews UI
.
viewModel
bindDataFrom
resizeSubviews
, prepareUI
, , .
/*----------------------------------------------------------------------
`subviews` UI .
----------------------------------------------------------------------*/
- (void) removeSubviews
{
// removing subviews from superview
for (UIView* subview in self.view.subviews){
[subview removeFromSuperview];
}
// remove sublayers from superlayer
for (CALayer* sublayer in self.view.layer.sublayers) {
[sublayer removeFromSuperlayer];
}
self.logoImgView = nil;
self.signInButton = nil;
self.signUpButton = nil;
self.gradient = nil;
}
, , - .
/*----------------------------------------------------------------------
subviews viewModel
----------------------------------------------------------------------*/
- (void) initSubviews:(LoginViewModel*)viewModel
{
if (self.view)
{
if (!self.logoImgView)
self.logoImgView = [[UIImageView alloc] init];
if (!self.signInButton)
self.signInButton = [UIButton buttonWithType:UIButtonTypeCustom];
if (!self.signUpButton)
self.signUpButton = [UIButton buttonWithType:UIButtonTypeCustom];
}
}
updateStyles
subviews
, .
/*----------------------------------------------------------------------
subviews. / /
----------------------------------------------------------------------*/
- (void) updateStyles:(LoginViewModel*)viewModel
{
if (!viewModel) return;
if (self.logoImgView)
[self styleFor_logoImgView:self.logoImgView vm:viewModel];
if (self.signInButton)
[self styleFor_signInButton:self.signInButton vm:viewModel];
if (self.signUpButton)
[self styleFor_signUpButton:self.signUpButton vm:viewModel];
}
.h
, oldViewModel
.
.
, .
, .
/*----------------------------------------------------------------------
subviews
----------------------------------------------------------------------*/
- (void) bindDataFrom:(LoginViewModel*)viewModel
{
// ,
if (([self.oldViewModel isEqualToModel:viewModel]) || (!viewModel)){
return;
}
[self.logoImgView setImage:[UIImage imageNamed:viewModel.imageName]];
[self.signInButton setTitle:viewModel.signInBtnTitle
forState:UIControlStateNormal];
[self.signUpButton setTitle:viewModel.signUpBtnTitle
forState:UIControlStateNormal];
self.oldViewModel = viewModel;
}
isEqualToModel
.
, , , , , , UI
.
isEqualToModel
NO
, .
:
/*----------------------------------------------------------------------
.
----------------------------------------------------------------------*/
- (BOOL) isEqualToModel:(LoginViewModel*)object
{
BOOL isEqual = YES;
if (![object.imageName isEqualToString:self.imageName]){
return NO;
}
if (![object.signInBtnTitle isEqualToString:self.signInBtnTitle]){
return NO;
}
if (![object.signUpBtnTitle isEqualToString:self.signUpBtnTitle]){
return NO;
}
return isEqual;
}
bindDataFrom
, resizeSubviews
, subviews
, .
/*----------------------------------------------------------------------
subviews.
.
----------------------------------------------------------------------*/
- (void) resizeSubviews:(LoginViewModel*)viewModel
{
//
if ((([self.oldViewModel isEqualToModel:self.viewModel]) &&
(CGSizeEqualToSize(self.oldSize, self.view.frame.size))) ||
(!viewModel)) {
return;
}
if (self.view){
if (self.logoImgView)
self.logoImgView.frame =
[LoginController rectFor_logoImgView:viewModel parentFrame:self.view.frame];
if (self.signInButton)
self.signInButton.frame =
[LoginController rectFor_signInButton:viewModel parentFrame:self.view.frame];
if (self.signUpButton)
self.signUpButton.frame =
[LoginController rectFor_signUpButton:viewModel parentFrame:self.view.frame];
if (self.gradient) self.gradient.frame = self.view.bounds;
}
self.oldSize = self.view.frame.size;
}
subviews
view
.
/*----------------------------------------------------------------------
subviews superView
----------------------------------------------------------------------*/
- (void) addSubviewsToSuperView
{
if (self.view){
if ((self.logoImgView) && (!self.logoImgView.superview))
[self.view addSubview:self.logoImgView];
if ((self.signInButton) && (!self.signInButton.superview))
[self.view addSubview:self.signInButton];
if ((self.signUpButton) && (!self.signUpButton.superview))
[self.view addSubview:self.signUpButton];
}
}
+Managment
-, +Styles
.
postUIsetting
UI
, .
, gestures
, , ..
- (void) postUIsetting
{
UIColor* firstColor =
[UIColor colorWithRed: 0.54 green: 0.36 blue: 0.79 alpha: 1.00];
UIColor* secondColor =
[UIColor colorWithRed: 0.41 green: 0.59 blue: 0.88 alpha: 1.00];;
self.gradient = [CAGradientLayer layer];
self.gradient.frame = self.view.bounds;
self.gradient.startPoint = CGPointZero;
self.gradient.endPoint = CGPointMake(1, 1);
self.gradient.colors = [NSArray arrayWithObjects:(id)firstColor.CGColor,
(id)secondColor.CGColor, nil];
[self.view.layer insertSublayer:self.gradient atIndex:0];
}
+Styles
+Managment
, +Styles
, UI
.
.
- (void) styleFor_logoImgView:(UIImageView*)imgView vm:(LoginViewModel*)viewModel
{
if (!imgView.isStylized){
imgView.contentMode = UIViewContentModeScaleAspectFit;
imgView.backgroundColor = [UIColor clearColor];
imgView.opaque = YES;
imgView.clipsToBounds = YES;
imgView.layer.masksToBounds = YES;
imgView.alpha = 1.0f;
imgView.isStylized = YES;
}
}
isStylized
, UIView
.
UI
.
, ( ), , , ..
+Frames
+Frames
+Styles
, , subviews
.
- , , , .
, , +Frames
(+
).
, , subviews
, .
+ (CGRect) rectFor_signUpButton:(LoginViewModel*)viewModel
parentFrame:(CGRect)parentFrame
{
if (CGRectEqualToRect(CGRectZero, parentFrame)) return CGRectZero;
// Calculating...
return rect;
}
, MFS
.
, , , UIViewController
(UI
), .
, UITableViewController
, - .
MFS
.
MFS
, 60FPS
.