MFS: un patrón para crear UI en aplicaciones iOS

La lógica del desarrollo de aplicaciones móviles es la complicación gradual de la carga funcional en la interfaz de usuario.

Esto, a su vez, conduce al crecimiento del código base y a la dificultad de mantenerlo.





MFS



- le permite crear un diseño moderno de aplicaciones y al mismo tiempo evitar un fenómeno como MassiveViewController



.





Foto: 10 años de la App Store: la evolución del diseño de las primeras aplicaciones - 9to5Mac





Razones para crear un patrón

 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