从Xcode 8和iOS10开始,viewDidLayoutSubviews上的视图大小不正确

时间:2016-09-19 17:09:18

标签: ios autolayout ios10 xcode8

似乎使用Xcode 8,在viewDidLoad上,所有viewcontroller子视图都具有相同的1000x1000大小。奇怪的是,但是没关系,viewDidLoad从来就不是正确调整视图大小的好地方。

viewDidLayoutSubviews是!

在我目前的项目中,我尝试打印按钮的大小:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    NSLog(@"%@", self.myButton);
}

日志显示myButton的大小为(1000x1000)!然后,如果我登录按钮单击,例如,日志显示正常大小。

我正在使用自动布局。

这是一个错误吗?

13 个答案:

答案 0 :(得分:95)

现在,Interface Builder允许用户动态更改故事板中每个视图控制器的大小,以模拟某个设备的大小。

在此功能之前,用户应手动设置每个视图控制器大小。因此视图控制器以一定的大小保存,在initWithCoder中用于设置初始帧。

现在,似乎initWithCoder不使用故事板中定义的大小,并为viewcontroller视图定义了1000x1000像素大小&所有的子视图。

这不是问题,因为视图应始终使用以下任一布局解决方案:

  • 自动布局,所有约束都会正确布局您的观点

  • autoresizingMask,它将布置没有附加任何约束的每个视图( note autolayout和margin constraints现在在同一视图中兼容\ o /!

是所有与视图层相关的布局内容的问题,例如cornerRadius,因为 autolayout autoresizing mask 适用于图层属性。

要回答此问题,常见的方法是使用viewDidLayoutSubviews(如果您在控制器中),或layoutSubview(如果您在视图中)。此时(不要忘记调用他们的super相对方法),你很确定所有的布局都已经完成了!

相当肯定吗?嗯...不完全,我已经说过,这就是我问这个问题的原因,在某些情况下,这个方法的视图仍然有1000x1000的大小。我认为我自己的问题没有答案。要提供有关它的最大信息:

1-只有在铺设细胞时才会发生!在UITableViewCell&在子视图正确布局后,UICollectionViewCell子类layoutSubview将不会被调用

2-正如@EugenDimboiu所说的那样(如果对你有用,请选择他的答案),在未布局的子视图上调用[myView layoutIfNeeded]将及时正确布局。

- (void)layoutSubviews {
    [super layoutSubviews];
    NSLog (self.myLabel); // 1000x1000 size 
    [self.myLabel layoutIfNeeded];
    NSLog (self.myLabel); // normal size
}

3-我认为,这绝对是一个错误。我已将它提交给雷达(id 28562874)。

PS:我不是英国人,如果我的语法应该纠正,请随时编辑我的帖子;)

PS2:如果你有更好的解决方案,请随意不要写另一个答案。我会移动接受的答案。

答案 1 :(得分:39)

您是否使用圆角作为按钮? 请先尝试拨打layoutIfNeeded()

答案 2 :(得分:18)

我知道这不是你的确切问题,但我遇到了类似的问题,尽管在viewDidLayoutSubviews中有一个正确的帧大小,我的一些观点被搞砸了。根据iOS 10发行说明:

  

“发送layoutIfNeeded到视图不会移动视图,   但在早期版本中,如果视图有   translatesAutoresizingMaskIntoConstraints设置为NO,如果是的话   通过约束定位,layoutIfNeeded将视图移动到   在将布局发送到子树之前匹配布局引擎。这些   更改纠正了这种行为,以及接收者的位置和通常   它的大小不受layoutIfNeeded的影响。

     

某些现有代码可能依赖于这种不正确的行为   现在纠正了。之前链接的二进制文件没有行为更改   iOS 10,但在iOS 10上构建时,您可能需要更正一些   通过发送-layoutIfNeeded到的超级视图的情况   translatesAutoresizingMaskIntoConstraints视图是前一个   接收器,或者在之前(或之后)定位和调整大小   取决于你想要的行为)layoutIfNeeded。

     

使用自动布局的自定义UIView子类的第三方应用程序   在调用super之前覆盖layoutSubviews和self上的脏布局   有可能在重建时触发布局反馈循环   iOS 10.当正确发送后续layoutSubviews调用时   他们必须确保在某些时候停止弄脏自己的布局(注意   在iOS 10之前的版本中跳过了这个调用。“

如果使用translatesAutoresizingMaskIntoConstraints,基本上你不能在View的子对象上调用layoutIfNeeded - 现在调用layoutIfNeeded必须在superView上,你仍然可以在viewDidLayoutSubviews中调用它。

答案 3 :(得分:18)

解决方案:将viewDidLayoutSubviews内的所有内容包裹在DispatchQueue.main.async中。

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    DispatchQueue.main.async {
        // do stuff here
    }
}

答案 4 :(得分:3)

这为我解决了(荒谬烦人的)问题:

- (void) viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height);

}

编辑/注意:这是一个全屏ViewController。

答案 5 :(得分:3)

如果layoutSubViews(它们不是)中的帧不正确,您可以在主线程上分配异步代码。这为系统提供了一些时间来进行布局。执行发送的块时,帧的大小合适。

答案 6 :(得分:2)

实际上viewDidLayoutSubviews也不是设置视图框架的最佳位置。据我所知,从现在开始,唯一应该在实际视图代码中使用layoutSubviews方法。我希望我不对,如果不是真的,请有人纠正我!

答案 7 :(得分:0)

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.

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
    }
}

答案 8 :(得分:0)

我的问题是通过改变

的用法来解决的
-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];
    self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}

所以,从Did到Will

超级奇怪

答案 9 :(得分:0)

更好的解决方案。

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_)
    }
}

使用

class CircleShapeComplement: LayoutComplementProtocol {
    func didLayoutSubviews(with targetView_: UIView) {
        targetView_.layer.cornerRadius = targetView_.frame.size.height / 2
    }
}

myButton.add(layoutComplement: CircleShapeComplement())

答案 10 :(得分:0)

覆盖layoutSublayers(图层:CALayer),而不是单元子视图中的layoutSubviews,以拥有正确的框架

答案 11 :(得分:0)

如果您需要根据视图的框架进行操作-覆盖layoutSubviews并调用layoutIfNeeded

    override func layoutSubviews() {
    super.layoutSubviews()

    yourView.layoutIfNeeded()
    setGradientForYourView()
}

我遇到了一个问题, viewDidLayoutSubviews返回了错误的帧,我需要为其添加渐变。而且只有layoutIfNeeded做正确的事情:)

答案 12 :(得分:-1)

根据ios中的新更新,这实际上是一个错误,但我们可以通过使用 -

来减少这种情况

如果您在项目中使用带有自动布局的xib,则必须在自动布局设置中更新框架,请为此找到图像。enter image description here