The pit of iOS layoutMargins: a long-lived bug

Magical effect

One day, when I returned to my seat, Zhang’s dying colleague was like a savior who grabbed me: “Cang potato shovel, it’s not good, you see it like this!!”

When I saw it, I couldn't help but say a sloppy potato: "I...go, I have been doing this for so many years. iOS has never met such a thing." I also called the leader. The leader took it for a while and then said, "Ha ha ha, I really feel that I want to achieve this effect, it is not so easy..."

What kind of bug is it that makes us so calm? Look at the gif below and you will know:

This square-shaped cell is an ordinary and ordinary collectionViewCell on an ordinary and ordinary collectionView. It has been used in many places. It has been used for more than a year. It has always been like this, and there has never been any problem. However, our fresh graduates did not know how to change it. The effect appeared: when the cell scrolled to the edge of the screen and was about to leave the screen, it seemed to be reluctant to leave, and actually shrank itself...

Would you like to help me debug?

Here's the code that reproduces the bug and can be reproduced on the iPhone 7 iOS 11 emulator. In order to write only one file, I have simplified the code as long as 60 lines:

import UIKit

final class TestCell: UICollectionViewCell {

  override init(frame: CGRect) {
    let imageView = UIImageView(frame: .zero)
    let metadataView = UIView(frame: .zero)

    super.init(frame: frame)

    imageView.backgroundColor = UIColor.red
    metadataView.backgroundColor = UIColor.green

    for view in [imageView, metadataView] {
      addSubview(view)
      view.translatesAutoresizingMaskIntoConstraints = false
      view.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = true
      view.trailingAnchor.constraint(equalTo: self.layoutMarginsGuide.trailingAnchor).isActive = true
    }

    imageView.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor).isActive = true
    imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true

    metadataView.topAnchor.constraint(equalTo: imageView.bottomAnchor).isActive = true
    metadataView.heightAnchor.constraint(equalToConstant: 25).isActive = true
    metadataView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor).isActive = true
  }

  required public init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

final class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

  override func viewDidLoad() {
    super.viewDidLoad()

    self.collectionView!.contentInsetAdjustmentBehavior = .never
    self.collectionView!.register(TestCell.self, forCellWithReuseIdentifier: "Cell")
  }

  // MARK: UICollectionViewDataSource

  override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10
  }

  override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    return collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let measurementCell = TestCell()
    let width = (collectionView.bounds.size.width - 20) / 2.0
    measurementCell.widthAnchor.constraint(equalToConstant: width).isActive = true
    return CGSize(width: width, height: measurementCell.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height)
  }
}
Copy code

The constraint is the original way of writing the system. It may be used by third-party libraries. The native writing is not familiar. For a simple explanation, assume that red is the picture and green is the description:

  1. The left, right, and top of the image are constrained to the parent view, height = width
  2. Describe the left, right, and lower constraints to the parent view, the height is fixed 25, the top is attached to the bottom of the picture

The code came out, can you see what the problem is?

Several guesses

Q: Is there any problem with the layout? A: The simplest UICollectionViewFlowLayout is used... No overrides.

Q: Is it a constraint conflict? A: Do you think I have a problem with the constraints? There will be no conflicts.

Q: Is the Cell size wrong? A: The most common automatic calculation... Hit the log to see that it is correct. Moreover, even if there is a problem, the size will not be calculated in real time when scrolling... It is shrinking while rolling...

Q:view.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = true This oneself.layoutMarginsGuide.leadingAnchorWhat the hell, you can't use it.self.leadingAnchor? A: You guessed it... Because I want to save things.self.layoutMargins So constrained tolayoutMarginsGuideBut if it is changed to a constraint to the ordinaryself.leadingAnchorThere will be no problem.

Q: Is this product only bugs in specific situations, such as iOS 11 or iPhoneX A: Yes, it is iOS 11... Any mobile phone can be reproduced, but it does It has something to do with iPhoneX...

Is this a clever reader guessing what the problem is? :)

In fact, there is one less line.

To solve this problem is very simple, that is in the cellinitMethod plus one sentence

self.insetsLayoutMarginsFromSafeArea = false
Copy code

insetsLayoutMarginsFromSafeArea This property is for allUIViewThe default isYES(I don’t think this is too scientific) when it isYESTime of viewlayoutMargins It will be adjusted according to the safeArea. In this case, even iflayoutMargins Set to a fixed value likelayoutMargins = .zeroHowever, when it reaches the edge of the screen, its margins will gradually become larger, the intention is to let the child view automatically avoid the iPhoneX bangs. In this way, it is not surprising that the above magical effect of the above effect.

Benefits and pits of Layout Margins

To put it this way, it should actually be a very common problem. Why do you usually encounter a lot? I think it is because we are bound tolayoutMarginsGuide The situation is relatively small.

layoutMargins This set of things is very convenient for changing insets. For example, I write a very versatile thing, I hope to support users to change its insets at will. If I don't use layoutMargins, I need to maintain 4 constraints:

// properties
var leadingInsetConstraint: NSLayoutConstraint!
var trailingInsetConstraint: NSLayoutConstraint!
var topConstraint: NSLayoutConstraint!
var bottomConstraint: NSLayoutConstraint!

// during init
self.leadingInsetConstraint = someView.leadingAnchor.constraint(equalTo: self.leadingAnchor)
self.leadingInsetConstraint.isActive = true
self.trailingInsetConstraint = someView.trailingAnchor.constraint(equalTo: self.trailingAnchor)
self.trailingInsetConstraint.isActive = true
self.topInsetConstraint = someView.topAnchor.constraint(equalTo: self.topAnchor)
self.topInsetConstraint.isActive = true
self.bottomInsetConstraint = someView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
self.bottomInsetConstraint.isActive = true

// configuration
self.leadingInsetConstraint.constant = inset.left // Suppose we don't think about Arabic.
self.trailingInsetConstraint.constant = inset.right
self.topInsetConstraint.constant = inset.top
self.bottomInsetConstraint.constant = inset.bottom
Copy code

And if I uselayoutMaginsThis set of things, the above code can be simplified a lot, a property does not need to save:

// during init
self.leadingInsetConstraint = someView.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor)
self.trailingInsetConstraint = someView.trailingAnchor.constraint(equalTo: self.layoutMarginsGuide.trailingAnchor)
self.topInsetConstraint = someView.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor)
self.bottomInsetConstraint = someView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor)

// configuration
self.layoutMargins = insets
Copy code

If useddirectionalLayoutMarginsEven the Arabic case is handled automatically.

But it also has some pits, one of which is mentioned above. In addition, I randomly listed two:

  1. The default value of layoutMargins is actually not 0. This makes me never understand Apple's brain circuit. Its default value is UIEdgeInsets(8,8,8,8). Maybe 8 is the lucky number of an Apple engineer...
  2. The layout margins may not work correctly until the view hierarchy is added. This is quite strange. I used to need to addSubview and then set layoutMargins. In other words, I have the same magical bugs. I don’t know if the latest version of the system has been fixed...

Intelligent Recommendation

Micro-channel browser BUG stepped pit recorded on IOS (a) - Detection rich text editor tinymce blur event of failure

Q: H5 access page on the micro channel, wherein after the completion of the editing operation of the editor loses focus rolled back to the top of the page. After initialization is complete editor itse...

Vue BUG stepped pit

1. Why a fixed alphabet on the right, with mounted acquired position is wrong with it? But rather use the updated...

uniapp pit【Report Bug】

1. [Report Bug] Code report error read EBADF solve: Open port 2. [Report Bug] The small program tool reports Some selectors are not allowed in component wxss, including tag name selectors, ID selector...

People are not short-lived, horses are still long - dissatisfied with $ 9.4 Sun and IBM negotiations broke down

IBM's negotiations with Sun are on the verge of bankruptcy because Sun believes IBM's offer of $9.40 per share is too low. The person familiar with the matter said that the Sun board rejected the offi...

More Recommendation

Listing 3-7 Long-lived objects will enter the old era "In-depth understanding of the Java Virtual Machine" Zhou Zhiming

Since the virtual machine uses the idea of ​​generational collection to manage memory, when reclaiming memory, you must be able to identify which objects should be placed in the new generation and whi...

Long integer dated pit

In the work, and the third party, the date of the other party in order to save the storage space of the database, the long integer number stored in the varchar type. In the conversion show, it turned ...

A long pit related to .so

Inevitable introduction of third-party code in Android application development. If the risk of an open source project is relatively controllable, it is necessary to be cautious if the commercial SDK i...

Long type pit

First, look at this example: The results of the operation are as follows: Strange, why is it not equal? Let's take a look at the source code: The source code is shown that there is a static internal c...

Copyright  DMCA © 2018-2026 - All Rights Reserved - www.programmersought.com  User Notice

Top