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:
- The left, right, and top of the image are constrained to the parent view, height = width
- 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:
- 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...
- 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...