ios 由于 Xcode 8 和 iOS10,viewDidLayoutSubviews 上的视图大小不正确
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39578530/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Since Xcode 8 and iOS10, views are not sized properly on viewDidLayoutSubviews
提问by Martin
It seems that with Xcode 8, on viewDidLoad
, all viewcontroller subviews have the same size of 1000x1000. Strange thing, but okay, viewDidLoad
has never been the better place to correctly size the views.
似乎在 Xcode 8 上viewDidLoad
,所有 viewcontroller 子视图的大小都相同,为 1000x1000。奇怪的事情,但好吧,viewDidLoad
从来没有像正确调整视图大小的好地方。
But viewDidLayoutSubviews
is!
但是viewDidLayoutSubviews
是!
And on my current project, I try to print the size of a button:
在我当前的项目中,我尝试打印按钮的大小:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"%@", self.myButton);
}
The log shows a size of (1000x1000) for myButton! Then if I log on a button click, for example, the log shows a normal size.
日志显示 myButton 的大小为 (1000x1000)!然后,如果我登录按钮单击,例如,日志显示正常大小。
I'm using autolayout.
我正在使用自动布局。
Is it a bug?
这是一个错误吗?
回答by Martin
Now, Interface Builder lets the user change dynamically the size of every view controllers in storyboard, to simulate the size of a certain device.
现在,Interface Builder 允许用户动态更改故事板中每个视图控制器的大小,以模拟特定设备的大小。
Before this functionality, the user should set manually each view controller size. So the view controller was saved with a certain size, which was used in initWithCoder
to set the initial frame.
在此功能之前,用户应该手动设置每个视图控制器的大小。因此视图控制器被保存为一定大小,用于initWithCoder
设置初始帧。
Now, it seems that initWithCoder
do not use the size defined in storyboard, and define a 1000x1000 px size for the viewcontroller view & all its subviews.
现在,似乎initWithCoder
不使用故事板中定义的大小,而是为 viewcontroller 视图及其所有子视图定义 1000x1000 px 大小。
This is not a problem, because views should always use either of these layout solutions:
这不是问题,因为视图应始终使用以下任一布局解决方案:
autolayout, and all the constraints will layout correctly your views
autoresizingMask, which will layout each view which doesn't have any constraint attached to (note autolayout and margin constraints are now compatible in the same view \o/ !)
自动布局,所有约束都会正确布局您的视图
autoresizingMask,它将布局每个没有附加任何约束的视图(注意 autolayout 和边距约束现在在同一视图中兼容 \o/ !)
But this isa problem for all layout stuff related to the view layer, like cornerRadius
, since neither autolayoutnor autoresizing maskapplies to layer properties.
但这对于与视图层相关的所有布局内容来说都是一个问题,例如cornerRadius
,因为autolayout和autoresizing mask都不适用于图层属性。
To answer this problem, the common way is to use viewDidLayoutSubviews
if you are in the controller, or layoutSubview
if you are in a view. At this point (don't forget to call their super
relative methods), you are pretty sure that all layout stuff has been done!
要回答这个问题,常见的方法是使用viewDidLayoutSubviews
if you are in the controller ,或者layoutSubview
if you are in a view 。此时(不要忘记调用它们的super
相关方法),您很确定所有布局都已完成!
Prettysure? Hum... not totally, I've remarked, and that's why I asked this question, in some cases the view still has its 1000x1000 size on this method. I think there is no answer to my own question. To give the maximum information about it:
很确定?嗯...不完全是,我已经说过,这就是我问这个问题的原因,在某些情况下,视图在此方法上仍然具有 1000x1000 大小。我认为我自己的问题没有答案。要提供有关它的最大信息:
1- it happends only when laying out cells! In UITableViewCell
& UICollectionViewCell
subclasses, layoutSubview
won't be called aftersubviews would be correctly layed out.
1-它仅在布置单元格时发生!在UITableViewCell
&UICollectionViewCell
子类中,在子视图正确布局后layoutSubview
不会被调用。
2- As @EugenDimboiu remarked (please upvote his answer if useful for you), calling [myView layoutIfNeeded]
on the not-layed out subview will layout it correctly just in time.
2- 正如@EugenDimboiu 所说(如果对您有用,请支持他的答案),调用[myView layoutIfNeeded]
未布局的子视图将及时正确布局。
- (void)layoutSubviews {
[super layoutSubviews];
NSLog (self.myLabel); // 1000x1000 size
[self.myLabel layoutIfNeeded];
NSLog (self.myLabel); // normal size
}
3- To my opinion, this is definitely a bug. I've submitted it to radar (id 28562874).
3-在我看来,这绝对是一个错误。我已将其提交给雷达(ID 28562874)。
PS: I'm not english native, so feel free to edit my post if my grammar should be corrected ;)
PS:我不是英语母语,所以如果我的语法应该更正,请随时编辑我的帖子;)
PS2: If you have any better solution, feel free not write another answer. I'll move the accepted answer.
PS2:如果您有更好的解决方案,请不要再写其他答案。我会移动已接受的答案。
回答by Eugen Dimboiu
Are you using rounded corners for your button ?
Try calling layoutIfNeeded()
before.
您是否为按钮使用圆角?layoutIfNeeded()
之前打电话试试。
回答by Derek Soike
Solution:Wrap everything inside viewDidLayoutSubviews
in DispatchQueue.main.async
.
解决方案:将所有内容都包裹viewDidLayoutSubviews
在DispatchQueue.main.async
.
// swift 3
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
// do stuff here
}
}
回答by HannahCarney
I know this wasn't your exact question, but I ran into a similar problem where as on the update some of my views were messed up despite having the correct frame size in viewDidLayoutSubviews. According to iOS 10 Release notes:
我知道这不是您的确切问题,但我遇到了类似的问题,在更新时,尽管 viewDidLayoutSubviews 中的帧大小正确,但我的一些视图还是搞砸了。根据 iOS 10 发行说明:
"Sending layoutIfNeeded to a view is not expected to move the view, but in earlier releases, if the view had translatesAutoresizingMaskIntoConstraints set to NO, and if it was being positioned by constraints, layoutIfNeeded would move the view to match the layout engine before sending layout to the subtree. These changes correct this behavior, and the receiver's position and usually its size won't be affected by layoutIfNeeded.
Some existing code may be relying on this incorrect behavior that is now corrected. There is no behavior change for binaries linked before iOS 10, but when building on iOS 10 you may need to correct some situations by sending -layoutIfNeeded to a superview of the translatesAutoresizingMaskIntoConstraints view that was the previous receiver, or else positioning and sizing it before (or after, depending on your desired behavior) layoutIfNeeded.
Third party apps with custom UIView subclasses using Auto Layout that override layoutSubviews and dirty layout on self before calling super are at risk of triggering a layout feedback loop when they rebuild on iOS 10. When they are correctly sent subsequent layoutSubviews calls they must be sure to stop dirtying layout on self at some point (note that this call was skipped in release prior to iOS 10)."
“向视图发送 layoutIfNeeded 不会移动视图,但在早期版本中,如果视图已将 translatesAutoresizingMaskIntoConstraints 设置为 NO,并且如果它被约束定位,则 layoutIfNeeded 将在发送布局之前移动视图以匹配布局引擎到子树。这些更改纠正了此行为,并且接收器的位置及其大小通常不会受到 layoutIfNeeded 的影响。
某些现有代码可能依赖于现在已更正的这种错误行为。在 iOS 10 之前链接的二进制文件没有行为更改,但是在 iOS 10 上构建时,您可能需要通过将 -layoutIfNeeded 发送到作为前一个接收器的 translatesAutoresizingMaskIntoConstraints 视图的超级视图来纠正某些情况,或者在之前对其进行定位和调整大小(或之后,取决于您想要的行为)layoutIfNeeded。
具有自定义 UIView 子类的第三方应用程序使用自动布局覆盖 layoutSubviews 和在调用 super 之前在 self 上的脏布局,当它们在 iOS 10 上重建时有触发布局反馈循环的风险。当他们正确发送后续 layoutSubviews 调用时,他们必须确保在某个时候停止弄脏自己的布局(请注意,在 iOS 10 之前的版本中已跳过此调用)。”
Essentially you cannot call layoutIfNeeded on a child object of the View if you are using translatesAutoresizingMaskIntoConstraints - now calling layoutIfNeeded has to be on the superView, and you can still call this in viewDidLayoutSubviews.
本质上,如果您使用 translatesAutoresizingMaskIntoConstraints,则不能在 View 的子对象上调用 layoutIfNeeded - 现在调用 layoutIfNeeded 必须在 superView 上,您仍然可以在 viewDidLayoutSubviews 中调用它。
回答by Emiel
If the frames are not correct in layoutSubViews (which they are not) you can dispatch async a bit of code on the main thread. This gives the system some time to do the layout. When the block you dispatch is executed, the frames have their proper sizes.
如果 layoutSubViews 中的框架不正确(它们不是),您可以在主线程上异步调度一些代码。这给了系统一些时间来进行布局。当您分派的块被执行时,帧有其适当的大小。
回答by Nerdhappy
This fixed the (ridiculously annoying) issue for me:
这为我解决了(非常烦人的)问题:
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height);
}
Edit/Note: This is for a full screen ViewController.
编辑/注意:这是一个全屏 ViewController。
回答by alex_roudique
Actually viewDidLayoutSubviews
also is not the best place to set frame of your view. As far as I understood, from now on the only place it should be done is layoutSubviews
method in the actual view's code. I wish I wasn't right, someone correct me please if it is not true!
实际上viewDidLayoutSubviews
也不是设置视图框架的最佳位置。据我了解,从现在开始,唯一应该做的就是layoutSubviews
实际视图代码中的方法。我希望我是对的,如果不是真的,请有人纠正我!
回答by Michal Zaborowski
I already reported this issue to apple, this issue exist since a long time, when you are initializing UIViewController from Xib, but i found quite nice workaround. In addition to that i found that issue in some cases when layoutIfNeeded on UICollectionView and UITableView when datasource is not set in initial moment, and needed also swizzle it.
我已经向苹果报告了这个问题,这个问题存在很长时间了,当你从 Xib 初始化 UIViewController 时,但我发现了很好的解决方法。除此之外,我发现在某些情况下,当 UICollectionView 和 UITableView 上的 layoutIfNeeded 在初始时刻未设置数据源时出现问题,并且还需要对其进行调整。
extension UIViewController {
open override class func initialize() {
if self !== UIViewController.self {
return
}
DispatchQueue.once(token: "io.inspace.uiviewcontroller.swizzle") {
ins_applyFixToViewFrameWhenLoadingFromNib()
}
}
@objc func ins_setView(view: UIView!) {
// View is loaded from xib file
if nibBundle != nil && storyboard == nil && !view.frame.equalTo(UIScreen.main.bounds) {
view.frame = UIScreen.main.bounds
view.layoutIfNeeded()
}
ins_setView(view: view)
}
private class func ins_applyFixToViewFrameWhenLoadingFromNib() {
UIViewController.swizzle(originalSelector: #selector(setter: UIViewController.view),
with: #selector(UIViewController.ins_setView(view:)))
UICollectionView.swizzle(originalSelector: #selector(UICollectionView.layoutSubviews),
with: #selector(UICollectionView.ins_layoutSubviews))
UITableView.swizzle(originalSelector: #selector(UITableView.layoutSubviews),
with: #selector(UITableView.ins_layoutSubviews))
}
}
extension UITableView {
@objc fileprivate func ins_layoutSubviews() {
if dataSource == nil {
super.layoutSubviews()
} else {
ins_layoutSubviews()
}
}
}
extension UICollectionView {
@objc fileprivate func ins_layoutSubviews() {
if dataSource == nil {
super.layoutSubviews()
} else {
ins_layoutSubviews()
}
}
}
Dispatch once extension:
分派一次延期:
extension DispatchQueue {
private static var _onceTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block: (Void) -> Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}
Swizzle extension:
混合扩展:
extension NSObject {
@discardableResult
class func swizzle(originalSelector: Selector, with selector: Selector) -> Bool {
var originalMethod: Method?
var swizzledMethod: Method?
originalMethod = class_getInstanceMethod(self, originalSelector)
swizzledMethod = class_getInstanceMethod(self, selector)
if originalMethod != nil && swizzledMethod != nil {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
return true
}
return false
}
}
回答by Jan
My issue was solved by changing the usage from
我的问题已通过更改用法解决
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
to
到
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
So, from Did to Will
所以,从Did到Will
Super weird
超级奇怪
回答by bizhara
Better solution for me.
对我来说更好的解决方案。
protocol LayoutComplementProtocol {
func didLayoutSubviews(with targetView_: UIView)
}
private class LayoutCaptureView: UIView {
var targetView: UIView!
var layoutComplements: [LayoutComplementProtocol] = []
override func layoutSubviews() {
super.layoutSubviews()
for layoutComplement in self.layoutComplements {
layoutComplement.didLayoutSubviews(with: self.targetView)
}
}
}
extension UIView {
func add(layoutComplement layoutComplement_: LayoutComplementProtocol) {
func findLayoutCapture() -> LayoutCaptureView {
for subView in self.subviews {
if subView is LayoutCaptureView {
return subView as? LayoutCaptureView
}
}
let layoutCapture = LayoutCaptureView(frame: CGRect(x: -100, y: -100, width: 10, height: 10)) // not want to show, want to have size
layoutCapture.targetView = self
self.addSubview(layoutCapture)
return layoutCapture
}
let layoutCapture = findLayoutCapture()
layoutCapture.layoutComplements.append(layoutComplement_)
}
}
Using
使用
class CircleShapeComplement: LayoutComplementProtocol {
func didLayoutSubviews(with targetView_: UIView) {
targetView_.layer.cornerRadius = targetView_.frame.size.height / 2
}
}
myButton.add(layoutComplement: CircleShapeComplement())