MFS - a pattern for building UI in iOS applications

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







, , , .





+Frames







subviews



.





+Styles







subviews



.

,,, ..

, property



.









,  +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

















prepareUI











UI



, .

viewDidAppear



.





removeSubviews











subviews



superView



.

UI



.





initSubviews











subviews



.





updateStyles











subviews



, , .

, .





bindDataFrom











subviews



.





resizeSubviews











subviews



.





addSubviewsToSuperView











subviews



superView



.





postUIsetting











 subviews





,  statusBar



,gestures



,allowSelectation



.









, UI



:





  1.  subviews



     UI



    .

    ( ).





  2.  subviews



    .





  3.  subviews



    (/ ).





  4.  subviews



    .





  5.  frames



     subviews



    .





  6.  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



.










All Articles