Real-time display of iOS UI code writing effect

Real-time display of iOS UI code writing effect

[[144355]]

There are two ways to write iOS application UI, one is Storyboard/Xib, and the other is hand-written code. Storyboard/Xib organizes UI. Because it provides visualization features, you can display the results by dragging UI controls from the UI library, which greatly improves the development speed. But there is a problem with multi-person collaborative development. Since all UIs are placed in the same Storyboard file, conflicts will occur when merging codes using Git/SVN. Multi-person collaborative development is not the main problem. Some people have proposed to create multiple Storyboards to separate UI writing. The main problem of Storyboard/Xib is that the code reusability is relatively poor. So some people choose to hand-write UI code, which not only solves the problem of multi-person collaborative development, but also uses custom controls in multiple Views. But every time you write UI code by hand, you have to compile, build and run it, and finally display it in the simulator, which will slow down the development speed. If you save the changes every time you modify the UI control, the modified results will be displayed in the simulator in real time, which can greatly improve the speed of writing UI.

Auto Layout

What is Auto Layout

Auto Layout is a constraint-based layout system that adjusts the position and size of UI elements according to the constraint relationship between UI elements.

What problems does Auto Layout solve?

  • Easier to adapt to screens of devices with different resolutions (iPhone 6 Plus, iPhone 6, iPhone 5s/5, iPhone 4s/4)
  • No additional processing is required when the device is rotated
  • Using constraints to describe layout logic is easier to understand and clear

How to use Auto Layout

The constraint class in Auto Layout corresponds to NSLayoutConstraint, and there are two main ways to create NSLayoutConstraint objects. The first is

  1. + (id)constraintWithItem:(id)view1
  2. attribute:(NSLayoutAttribute)attribute1
  3. relatedBy:(NSLayoutRelation)relation
  4. toItem:(id)view2
  5. attribute:(NSLayoutAttribute)attribute2
  6. multiplier:(CGFloat)multiplier
  7. constant:(CGFloat)constant;

The main meaning of the above method is that the attribute1 of a view1 is equal to (less than or equal to/greater than or equal to) the multiplier of the attribute2 of a view2 plus the constant. The attribute mainly consists of the following values ​​representing the position (up/down/left/right) and size (width/height):

  1. typedef enum : NSInteger {
  2. NSLayoutAttributeLeft = 1 ,
  3. NSLayoutAttributeRight,
  4. NSLayoutAttributeTop,
  5. NSLayoutAttributeBottom,
  6. NSLayoutAttributeLeading,
  7. NSLayoutAttributeTrailing,
  8. NSLayoutAttributeWidth,
  9. NSLayoutAttributeHeight,
  10. NSLayoutAttributeCenterX,
  11. NSLayoutAttributeCenterY,
  12. NSLayoutAttributeBaseline,
  13. NSLayoutAttributeNotAnAttribute = 0  
  14. }NSLayoutAttribute;

Simplified, the formula can be expressed as:

  1. view1.attribute1 = view2.attribute2 * multiplier + constant

The second way is:

  1. + (NSArray *)constraintsWithVisualFormat:(NSString *)format
  2. options:(NSLayoutFormatOptions)opts
  3. metrics:(NSDictionary *)metrics
  4. views:(NSDictionary *)views;

This method mainly uses Visual Format Language to describe constraint layout. Although the syntax is relatively concise, it is less readable and prone to errors.

Problems with Auto Layout

Although Auto Layout is very powerful and flexible in laying out views, the syntax for creating constraints is too complicated. Here is an example from Masonry:

  1. UIView *superview = self;
  2.  
  3. UIView *view1 = [[UIView alloc] init];
  4. view1.translatesAutoresizingMaskIntoConstraints = NO;
  5. view1.backgroundColor = [UIColor greenColor];
  6. [superview addSubview:view1];
  7.  
  8. UIEdgeInsets padding = UIEdgeInsetsMake( 10 , 10 , 10 , 10 );
  9.  
  10. [superview addConstraints:@[
  11.  
  12. //view1 constraints  
  13. [NSLayoutConstraint constraintWithItem:view1
  14. attribute:NSLayoutAttributeTop
  15. relatedBy:NSLayoutRelationEqual
  16. toItem:superview
  17. attribute:NSLayoutAttributeTop
  18. multiplier: 1.0  
  19. constant:padding.top],
  20.  
  21. [NSLayoutConstraint constraintWithItem:view1
  22. attribute:NSLayoutAttributeLeft
  23. relatedBy:NSLayoutRelationEqual
  24. toItem:superview
  25. attribute:NSLayoutAttributeLeft
  26. multiplier: 1.0  
  27. constant:padding.left],
  28.  
  29. [NSLayoutConstraint constraintWithItem:view1
  30. attribute:NSLayoutAttributeBottom
  31. relatedBy:NSLayoutRelationEqual
  32. toItem:superview
  33. attribute:NSLayoutAttributeBottom
  34. multiplier: 1.0  
  35. constant:-padding.bottom],
  36.  
  37. [NSLayoutConstraint constraintWithItem:view1
  38. attribute:NSLayoutAttributeRight
  39. relatedBy:NSLayoutRelationEqual
  40. toItem:superview
  41. attribute:NSLayoutAttributeRight
  42. multiplier: 1  
  43. constant:-padding.right],
  44.  
  45. ]];

If you need to write so many lines of code for such a simple example, imagine how painful it would be to create constraints for multiple views. Another way is to use Visual Format Language (VFL), which has a simpler syntax but poor readability and is prone to errors.

Masonry

Why use Masonry

Masonry uses a chained DSL (Domain-specific language) to encapsulate NSLayoutConstraint. This makes Auto Layout layout code easier to read and concise.
Use Masonry's MASConstraintMaker to express the same constraint

  1. UIEdgeInsets padding = UIEdgeInsetsMake( 10 , 10 , 10 , 10 );
  2.  
  3. [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
  4. make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler  
  5. make.left.equalTo(superview.mas_left).with.offset(padding.left);
  6. make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
  7. make.right.equalTo(superview.mas_right).with.offset(-padding.right);
  8. }];

Even shorter

  1. [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
  2. make.edges.equalTo(superview).with.insets(padding);
  3. }];

#p#

How to use

There are three ways to use Masonry to create constraints to define layouts: mas_makeConstraints, mas_updateConstraints, and mas_remakeConstraints.

1. mas_makeConstraints

After creating a constraint using mas_makeConstraints, you can use a local variable or attribute to save it for next reference; if you create multiple constraints, you can use an array to save them.

  1. // in public/private interface  
  2. @property (nonatomic, strong) MASConstraint *topConstraint;
  3.  
  4. ...
  5.  
  6. // when making constraints  
  7. [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
  8. self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);
  9. make.left.equalTo(superview.mas_left).with.offset(padding.left);
  10. }];
  11.  
  12. ...
  13. // then later you can call  
  14. [self.topConstraint uninstall];

2. mas_updateConstraints

Sometimes you need to update constraints (for example, animation and debugging) instead of creating fixed constraints. You can use the mas_updateConstraints method.

  1. // this is Apple's recommended place for adding/updating constraints  
  2. // this method can get called multiple times in response to setNeedsUpdateConstraints  
  3. // which can be called by UIKit internally or in your code if you need to trigger an update to your constraints  
  4. - ( void )updateConstraints {
  5. [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
  6. make.center.equalTo(self);
  7. make.width.equalTo(@(self.buttonSize.width)).priorityLow();
  8. make.height.equalTo(@(self.buttonSize.height)).priorityLow();
  9. make.width.lessThanOrEqualTo(self);
  10. make.height.lessThanOrEqualTo(self);
  11. }];
  12.  
  13. //according to apple super should be called at end of method  
  14. [ super updateConstraints];
  15. }

3. mas_remakeConstraints

mas_remakeConstraints is similar to mas_updateConstraints, both of which update constraints. However, mas_remakeConstraints deletes the previous constraint and then adds a new constraint (suitable for moving animations); while mas_updateConstraints only updates the value of the constraint.

  1. - ( void )changeButtonPosition {
  2. [self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
  3. make.size.equalTo(self.buttonSize);
  4.  
  5. if (topLeft) {
  6. make.top.and.left.offset( 10 );
  7. } else {
  8. make.bottom.and.right.offset(- 10 );
  9. }
  10. }];
  11. }

To learn more about the above three code snippets, you can download the Masonry iOS Examples project for reference.

Classy

Classy Introduction and Features

Classy is a stylesheet system that can be seamlessly integrated with UIKit. It borrows the ideas of CSS, but introduces new syntax and naming rules.

Flexible built-in syntax

{ } : ; These syntax symbols are optional, you can choose to express your stylesheet in a way that suits you.

You can use { } : ; to limit the stylesheet

  1. $main-color = #e1e1e1;
  2.  
  3. MYCustomView {
  4. background-color: $main-color;
  5. title-insets: 5 , 10 , 5 , 10 ;
  6. > UIProgressView.tinted {
  7. progress-tint-color: black;
  8. track-tint-color: yellow;
  9. }
  10. }
  11.  
  12. ^UIButton.warning, UIView.warning ^UIButton {
  13. title-color[state:highlighted]: #e3e3e3;
  14. }

Or you can use spaces to delimit the stylesheet

  1. $main-color = #e1e1e1
  2.  
  3. MYCustomView
  4. background-color $main-color
  5. title-insets 5 , 10 , 5 , 10  
  6. > UIProgressView.tinted
  7. progress-tint-color black
  8. track-tint-color yellow
  9.  
  10. ^UIButton.warning, UIView.warning ^UIButton
  11. title-color[state:highlighted] #e3e3e3

Default Style

Classy looks for a stylesheet file named stylesheet.cas in the application bundle by default. If you use this file name, you can load the stylesheet file without doing anything.
But if you want to specify another file path (style file name), you can create [CASStyler defaultStyler]

  1. [CASStyler defaultStyler].filePath = [[NSBundle mainBundle] pathForResource:@ "myStyles.cas" ofType:nil];

If you also want to get error information for debugging when an error occurs, you can use -(void)setFilePath:error:

  1. NSError *error = nil;
  2. NSString filePath = [[NSBundle mainBundle] pathForResource:@ "myStyles.cas" ofType:nil];
  3. [[CASStyler defaultStyler] setFilePath:filePath error:&error];

If you use Storyboard/Xib to organize the UI interface, you need to set the filePath in the int main(int argc, char * argv[]) method of main.m to ensure that the stylesheet is loaded before the UIWindow is created. Otherwise (using handwritten UI code), you set the filePath in the - (BOOL)application:didFinishLaunchingWithOptions: method of AppDelegate.m

Live Reload

Live Reload is a key feature that displays the effects of writing UI code in real time. It can check stylesheet file changes in real time without recompiling, building, and running the simulator, thus greatly improving development speed.
To enable Live Reload, you need to specify the stylesheet path and only run on the simulator.

  1. # if TARGET_IPHONE_SIMULATOR
  2. NSString *absoluteFilePath = CASAbsoluteFilePath(@ "../Styles/stylesheet.cas" );
  3. [CASStyler defaultStyler].watchFilePath = absoluteFilePath;
  4. #endif

Selectors

Style Selectors are a way to specify which view uses which style. There are three main ways to specify the target view:

  1. Object Class
  2. View Hierarchy
  3. Style Class

You can mix and match the three methods, as shown below:

  1. /* match views
  2. * where class is UIButton or UIButton subclass
  3. * and styleClass is "large"
  4. * and superview class is UITabBar
  5. */  
  6.  
  7. UITabBar > ^UIButton.large { }

For detailed usage, please refer to the Selectors section of the official website.

To avoid confusion with Objective-C message selectors, the term style selectors refers to selectors for classy stylesheets.

Properties

Classy supports all UIAppearance properties and methods, as well as many properties that are not related to UIAppearance. Classy uses the same property naming as UIKit, so you don't have to think about how to map style properties to Objective-C properties.
The properties of the UIPageControl class are as follows:

  1. @property (nonatomic,retain) UIColor *pageIndicatorTintColor;
  2. @property (nonatomic,retain) UIColor *currentPageIndicatorTintColor;

The name of the style property uses the same name as Objective-C

  1. UIPageControl {
  2. pageIndicatorTintColor black
  3. currentPageIndicatorTintColor purple
  4. }

The naming convention for style properties is kebab case.

  1. UIPageControl {
  2. page-indicator-tint-color black
  3. current-page-indicator-tint-color purple
  4. }

For detailed usage, please refer to the Properties section of the official website.

Keep it DRY(Don't Repeat Yourself)

A very important principle in programming is to avoid duplication, which can not only greatly reduce duplicate code, but also make the code easier to reuse and maintain. Classy provides three ways to avoid code duplication: grouping, nesting, variables

Grouping

If there are two or more style selectors sharing the same attribute

  1. UISlider.info {
  2. minimum-track-tint-color black
  3. maximum-track-tint-color purple
  4. }
  5.  
  6. UISlider.error {
  7. minimum-track-tint-color black
  8. maximum-track-tint-color purple
  9. thumb-tint-color red
  10. }

We can extract the same attributes into grouped style selectors

  1. UISlider.info, UISlider.error {
  2. minimum-track-tint-color black
  3. maximum-track-tint-color purple
  4. }
  5.  
  6. UISlider.error {
  7. thumb-tint-color red
  8. }

#p#

Nesting

If two or more style selectors share the same view hierarchy

  1. UICollectionView {
  2. background-color #a2a2a2
  3. }
  4.  
  5. UICollectionView > UICollectionViewCell {
  6. clips-to-bounds NO
  7. }
  8.  
  9. UICollectionView > UICollectionViewCell UILabel {
  10. text-color purple
  11. }
  12.  
  13. UICollectionView > UICollectionViewCell UILabel.title {
  14. font 20  
  15. }

We express the view hierarchies in this way through nesting

  1. UICollectionView {
  2. background-color #a2a2a2
  3.  
  4. > UICollectionViewCell {
  5. clips-to-bounds NO
  6.  
  7. UILabel {
  8. text-color purple
  9.  
  10. &.title {
  11. font 20  
  12. }
  13. }
  14. }
  15. }

Variables

Classy allows you to store multiple identical style property values ​​for sharing by defining variables. The variable naming rules are as follows:

  • Must start with an uppercase or lowercase letter or a $ sign
  • Can contain _, - or any alphanumeric characters

    1. // prefix with ' $ ' to help distinguish variables  
    2. $brand-color = #e1e1e1
    3.  
    4. // OR not  
    5. insets = 5 , 10 , 5 , 10  
    6.  
    7. UIButton {
    8. background-color $brand-color
    9. contentEdgeInsets insets
    10. background-image[state:selected] bg_button insets
    11. }

    *** The official also provides an example to explain how to use it: Custom Views Example

ClassyLiveLayout

ClassyLiveLayout combines Classy stylesheets with Masonry to create a tool that allows you to fine-tune Auto Layout constraints in real time in a running simulator.

ClassyLiveLayout has a core category: UIView+ClassyLayoutProperties, which defines the following properties in UIView:

  1. @property (nonatomic, assign) UIEdgeInsets cas_margin;
  2. @property (nonatomic, assign) CGSize cas_size;
  3.  
  4. // shorthand properties for setting only a single constant value  
  5. @property (nonatomic, assign) CGFloat cas_sizeWidth;
  6. @property (nonatomic, assign) CGFloat cas_sizeHeight;
  7.  
  8. @property (nonatomic, assign) CGFloat cas_marginTop;
  9. @property (nonatomic, assign) CGFloat cas_marginLeft;
  10. @property (nonatomic, assign) CGFloat cas_marginBottom;
  11. @property (nonatomic, assign) CGFloat cas_marginRight;

cas_margin and cas_size represent the position and size of UI elements respectively, while the rest of the properties are further subdivisions of the two properties. We can access style properties from stylesheets to define constraint layouts, separating data from code, which is conducive to modifying and reusing code.

  1. UIView.blue-box {
  2. cas_size: 80   100  
  3. cas_margin-top: 60  
  4. cas_margin-left: 50  
  5. }
  6.  
  7. UIView.red-box {
  8. cas_size-width: 120  
  9. cas_margin-left: 20  
  10. }

We can reference style properties when defining layouts in updateConstraints or updateViewConstrains

  1. - ( void )updateViewConstraints {
  2. [ super updateViewConstraints];
  3.  
  4. [_blueBoxView mas_updateConstraints:^(MASConstraintMaker *make) {
  5. make.width.equalTo(@(_blueBoxView.cas_size.width));
  6. make.height.equalTo(@(_blueBoxView.cas_size.height));
  7. make.top.equalTo(@(_blueBoxView.cas_margin.top));
  8. make.left.equalTo(@(_blueBoxView.cas_margin.left));
  9. }];
  10.  
  11. [_redBoxView mas_updateConstraints:^(MASConstraintMaker *make) {
  12. make.width.equalTo(@(_redBoxView.cas_size.width));
  13. make.height.equalTo(_blueBoxView);
  14. make.top.equalTo(_blueBoxView);
  15. make.left.equalTo(_blueBoxView.mas_right).with.offset(_redBoxView.cas_margin.left);
  16. }];
  17. }

When defining view layouts, Auto Layout constraints are placed in stylesheets and live reloaded. If you modify constraints, you can see the effect of the changes in real time without having to recompile, build, and run the simulator.

Sample Project

Configuration Project

Since Masonry, Classy and ClassyLiveLayout need to be referenced, the Podfile is configured as follows:

  1. pod 'Masonry' , '~> 0.6.1'  
  2. pod 'Classy' , '~> 0.2.4'  
  3. pod 'ClassyLiveLayout' , '~> 0.6.0'  

Writing Code

1. Add stylesheet.cas file to the project

After installing Masonry, Classy and ClassyLiveLayout, the first time you run the project, you will see an error message saying there is no stylesheet.cas file:

No stylesheet.cas file error.png

Just add an empty stylesheet.cas file to your project.

Create stylesheet.cas file.png

2. Create a LiveView class that inherits SHPAbstractView.

Create LiveView inherit SHPAbstractView.png

Create a LiveView object in ViewController and then reference it by self.view.

Setup root view in ViewController.png

When compiling and running, a compilation error occurs in SHPAbstractView.h because UIView cannot be found.

SHPAbstractView Compile error.png

#p#

The problem can be solved by simply introducing UIKit, but when running the application, the following error occurs:

Must override methods.png

The main reason is that any custom UIView that inherits SHPAbstractView needs to override two methods: - (void)addSubviews and - (void)defineLayout. We can see from the source code of SHPAbstractView:

SHPAbstractView Source Code .png

So just override two methods in the LiveView.m file

  1. #pragma mark - Add subviews and define layout
  2. - ( void )addSubviews
  3. {
  4. }
  5.  
  6. - ( void )defineLayout
  7. {
  8. }

3. LiveView class design

LiveView mainly consists of two properties: redBoxView and blueBoxView. redBoxView represents the red square, and blueBoxView represents the blue square.

  1. # import   "SHPAbstractView.h"  
  2.  
  3. @interface LiveView : SHPAbstractView
  4.  
  5. @property (strong, nonatomic) UIView *redBoxView;
  6. @property (strong, nonatomic) UIView *blueBoxView;
  7.  
  8. @end  

4. LiveView class implementation

Since the SHPAbstractView class has already handled how to initialize the View, it exposes two interfaces - (void)addSubviews and -(void)defineLayout to handle building the view hierarchy and defining the layout respectively. The subclass only needs to override these two methods of SHPAbstractView to create a LiveView.
However, we put all Auto Layout constraints in stylesheets for real-time loading (Live reload), that is, in the stylesheet.cas file of this project, to separate layout data from layout code.

  1. UIView.redBox {
  2. cas_marginTop 50  
  3. cas_marginLeft 20  
  4.  
  5. cas_size 100   100  
  6. }
  7.  
  8. UIView.blueBox {
  9. cas_marginTop 50  
  10. cas_marginRight - 20  
  11.  
  12. cas_size 100   100  
  13. }

Once you have the constraint data, you can lay it out in code:

  1. @implementation LiveView
  2.  
  3. #pragma mark - Add subviews and define layout
  4. - ( void )addSubviews
  5. {
  6. self.backgroundColor = [UIColor whiteColor];
  7. [self addSubview:self.redBoxView];
  8. [self addSubview:self.blueBoxView];
  9. }
  10.  
  11. - ( void )defineLayout
  12. {
  13. [self.redBoxView mas_updateConstraints:^(MASConstraintMaker* make){
  14. make.top.equalTo(@(self.redBoxView.cas_marginTop));
  15. make.left.equalTo(@(self.redBoxView.cas_marginLeft));
  16. make.width.equalTo(@(self.redBoxView.cas_sizeWidth));
  17. make.height.equalTo(@(self.redBoxView.cas_sizeHeight));
  18. }];
  19.  
  20. [self.blueBoxView mas_updateConstraints:^(MASConstraintMaker *make){
  21. make.top.equalTo(@(self.blueBoxView.cas_marginTop));
  22. make.right.equalTo(@(self.blueBoxView.cas_marginRight));
  23. make.width.equalTo(@(self.blueBoxView.cas_sizeWidth));
  24. make.height.equalTo(@(self.blueBoxView.cas_sizeHeight));
  25. }];
  26. }
  27.  
  28. #pragma mark - Lazy initialization
  29. - (UIView*)redBoxView
  30. {
  31. if (!_redBoxView) {
  32. _redBoxView = [UIView new ];
  33. _redBoxView.cas_styleClass = @ "redBox" ;
  34. _redBoxView.backgroundColor = [UIColor redColor];
  35. }
  36.  
  37. return _redBoxView;
  38. }
  39.  
  40. - (UIView*)blueBoxView
  41. {
  42. if (!_blueBoxView) {
  43. _blueBoxView = [UIView new ];
  44. _blueBoxView.cas_styleClass = @ "blueBox" ;
  45. _blueBoxView.backgroundColor = [UIColor blueColor];
  46. }
  47.  
  48. return _blueBoxView;
  49. }

5. The simulator supports Live Reload

To enable Live Reload, you need to specify the stylesheet path and only run on the simulator.

Support Live Reload.png

The effect at this time:

#p#

6. Separate style files

Because some netizens have raised the following question: If the styles of all views are placed in the same stylesheet.cas file, the stylesheet.cas file will be complicated, and it will not be easy to merge the code when multiple people collaborate on development. Therefore, it is necessary to separate the style files into multiple files.

  1. Create a variable.cas file and put the style of redBox corresponding to UIView in the variable.cas file.

    variable.cas file.png

  2. Use the @import directive in the stylesheet.cas style file to reference the variable.cas file

stylesheet.cas file.png

***Effect

Sample code storage address: LiveAutoLayout

Summarize

Before, every time you change the handwritten UI code, you usually have to recompile, build, and run the simulator to see the effect. But after using Masonry, Classy, ​​and ClassLiveLayout together, you can say goodbye to this time-consuming process and greatly improve the development speed. In addition, we put all Auto Layout constraints in stylesheets for real-time loading (Live reload), separate layout data from layout code, and make the code more reusable and maintainable. Classy also provides three ways to avoid duplication: Grouping, Nesting, and Variable, so that style data can be reused as much as possible.
This is my first time writing a technical blog. There may be many errors and loopholes. I hope you can give me more advice and I hope this article can help you.

<<:  Runtime series (analysis of data structure)

>>:  Cocos Play: The best solution for mobile web games

Recommend

WeChat cracks down on websites forcing users to click to read the full text

WeChat has begun to crack down on third-party web...

Answers to difficult questions about Xiaohongshu operation and promotion!

In view of the fact that everyone had many diffic...

BMW workers back strike over pension dispute

According to foreign media reports, workers at BM...

Do elephants drink snot when they drink water?

Reviewer: Chen Mingyong, Professor of the School ...

How does product operation achieve user fission?

Invitation is one of the ways to achieve product ...

Interesting stories about scientific discovery: Levitating frog

In 2000, a levitating frog won the Ig Nobel Prize...

Is the blind box marketing routine on hold?

The blind box circle doesn’t seem to be peaceful ...

Electric sweeper website SEO optimization training case

The latest SEO training practice case: Electric s...

SEM bidding hosting service content

The content of SEM bidding hosting provided by Ku...