モバイルアプリケーション開発のロジックは、ユーザーインターフェイスの機能的負荷が徐々に複雑になることです。
これは、コードベースの成長とそれを維持することの困難につながります。
MFS
-アプリケーションの最新のデザインを作成すると同時に、などの現象を回避できますMassiveViewController
。
写真:App Storeの10年:初期のアプリのデザインの進化-9to5Mac
パターンを作成する理由
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
.