由于Xcode 8和iOS10,ViewDidLayoutSubView上的视图大小不正确

由于Xcode 8和iOS10,ViewDidLayoutSubView上的视图大小不正确,ios,autolayout,ios10,xcode8,Ios,Autolayout,Ios10,Xcode8,似乎对于Xcode 8,在viewDidLoad上,所有viewcontroller子视图的大小都是1000x1000。奇怪的是,viewDidLoad从来都不是正确调整视图大小的好地方 但是viewdilayoutsubviews是 在我当前的项目中,我尝试打印按钮的大小: - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; NSLog(@"%@", self.myButton); } 日志显示myB

似乎对于Xcode 8,在
viewDidLoad
上,所有viewcontroller子视图的大小都是1000x1000。奇怪的是,
viewDidLoad
从来都不是正确调整视图大小的好地方

但是
viewdilayoutsubviews

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

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    NSLog(@"%@", self.myButton);
}
日志显示myButton的大小为(1000x1000)!然后,如果我登录一个按钮,例如,单击,日志显示正常大小

我正在使用自动布局


这是一个bug吗?

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

您的按钮是否使用圆角?
请尝试在之前调用
layoutifneed()

我知道这不是你的确切问题,但我遇到了一个类似的问题,在更新时,尽管ViewDidLayoutSubView中的帧大小正确,但我的一些视图还是被弄乱了。根据iOS 10发行说明:

“发送视图所需的布局不会移动视图, 但在早期版本中,如果视图 TranslatesAutoResizengMaskinToConstraints设置为否,如果为 通过约束定位,layoutIfNeeded会将视图移动到 在将布局发送到子树之前匹配布局引擎。这些 更改会纠正此行为,并且通常会纠正接收器的位置和位置 其大小不受所需布局的影响

某些现有代码可能依赖于以下错误行为: 现在更正。以前链接的二进制文件没有行为更改 iOS 10,但在iOS 10上构建时,可能需要更正一些错误 通过发送-layoutIfNeeded到 translatesAutoresizingMaskIntoConstraints视图是上一个 接收器,或在之前(或之后)对其进行定位和调整大小, 根据您想要的行为)需要进行布局

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

如果您正在使用TranslatesAutoResizengMaskintoConstraints,则基本上无法在视图的子对象上调用LayoutFeeded-现在调用LayoutFeeded必须在superView上,并且您仍然可以在ViewDidLayoutSubView中调用它。这修复了我的(可笑的恼人)问题:

- (void) viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

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

}

编辑/注意:这适用于全屏ViewController。

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

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

现在,似乎
initWithCoder
没有使用故事板中定义的大小,而是为viewcontroller视图及其所有子视图定义1000x1000 px大小

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

  • 自动布局,所有约束将正确布局视图

  • autoresizingMask,它将布局每个未附加任何约束的视图(注意autolayout和边距约束现在在同一视图中兼容\o/!)

但这对于所有与视图图层相关的布局内容来说都是一个问题,例如
拐角半径
,因为自动布局和自动调整大小遮罩都不适用于图层特性

要回答此问题,常用的方法是,如果您在控制器中,则使用
viewdilayoutsubview
;如果您在视图中,则使用
layoutSubview
。在这一点上(别忘了调用他们的
super
relative方法),你很确定所有的布局工作都已经完成了

相当确定吗?哼不完全是这样,我已经说过了,这就是为什么我问这个问题,在某些情况下,视图在这个方法上仍然有它的1000x1000大小。我想我自己的问题没有答案。要提供有关它的最大信息,请执行以下操作:

1-只有在布置单元时才会发生这种情况!在
UITableViewCell
UICollectionViewCell
子类中,子视图正确布局后,将不会调用
layoutSubview

2-正如@EugenDimboiu所说(如果对您有用,请更新他的答案),在未布局的子视图上调用
[myView layoutifneed]
将及时正确布局它

- (void)layoutSubviews {
    [super layoutSubviews];
    NSLog (self.myLabel); // 1000x1000 size 
    [self.myLabel layoutIfNeeded];
    NSLog (self.myLabel); // normal size
}
3-在我看来,这绝对是一个错误。我已经将它提交给雷达(id 28562874)

注:我不是英语母语,所以如果我的语法需要更正,请随意编辑我的帖子;)


PS2:如果你有更好的解决方案,请不要再写其他答案。我将移动已接受的答案。

如果LayoutSubView中的帧不正确(事实并非如此),您可以在主线程上调度一点异步代码。这给了系统一些时间来进行布局。当您分派的块被执行时,帧具有适当的大小。

我已经向苹果公司报告了这个问题,这个问题已经存在很长时间了,当您从Xib初始化UIViewController时,但我发现了非常好的解决方法。除此之外,我发现在某些情况下,当数据源未在初始时刻设置时,UICollectionView和UITableView上需要LayoutFneed时会出现这个问题,并且
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()
        }
    }
}
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()
    }
}
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
    }
}
-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];
    self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
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())
// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    DispatchQueue.main.async {
        // do stuff here
    }
}
    override func layoutSubviews() {
    super.layoutSubviews()

    yourView.layoutIfNeeded()
    setGradientForYourView()
}