Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support to UILayoutGuide #269

Merged
merged 20 commits into from
Oct 31, 2017
Merged

Adding support to UILayoutGuide #269

merged 20 commits into from
Oct 31, 2017

Conversation

corujautx
Copy link
Contributor

This pull-request adds support in Cartography to use UILayoutGuides and improves Cartography to work with iOS 11.0 new features.

There's a breaking change regarding UILayoutSupport as I made it simpler to constrain elements such as safeAreaLayoutGuide and general views. This breaking change reflects that you no longer will use topLayoutGuideCartography or bottomLayoutGuideCartography inside the constrain block but instead you pass a property of the view controller to the method (e.g. now named both car_topLayoutGuide and car_bottomLayoutGuide) so you can attach constraints to its height, top or bottom as anchors are specified by the documentation.

There's also a convenience property in the new ViewProxy that allows you to directly access a brand new LayoutProxy corresponding to the safeAreaLayoutGuide in iOS 11.0 as suggested by @gsampaio .

This should be able to close issues #268 and #267, and improve upon #207.

I look forward to suggestions in how to improve this pull request. Thanks.

@corujautx
Copy link
Contributor Author

corujautx commented Oct 16, 2017

Also, because I extended the number of constrain overloads this should also fix #263

@corujautx
Copy link
Contributor Author

corujautx commented Oct 16, 2017

The breaking change that this PR introduces is that code that today looks like this

constrain(view) { view in 
    view.top == vc.topLayoutGuideCartography
}

will change to:

constrain(view, vc.car_topLayoutGuide) { view, guide in 
     view.top == guide.bottom
}

This adds more flexibility as you can also now constrain to the top/height attributes of an UILayoutSupport.

The whole idea in this PR is to unify UIView (or NSView in macOS), UILayoutGuide and UILayoutSupport as constrainable elements, represented by the protocol LayoutElement.

Copy link

@inamiy inamiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! 👏👏👏

I like LayoutProxys splitted into multiple directions, and separating ViewProxy and LayoutGuideProxy so that ViewProxy.safeAreaLayoutGuide / LayoutGuideProxy.owningView is possible (I think we need test cases for this).

A bit downside is the use of as! casts in many places, but I guess it is needed unless we introduce new messy type-erased types.

For terminology, I personally prefer items over elements since "item" is used throughout Auto Layout system (e.g. NSLayoutConstraint(item:...)).


@available(iOS, introduced: 9.0)
@available(tvOS, introduced: 9.0)
public final class LayoutGuideProxy: SupportsPositioningLayoutProxy {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UILayoutGuide doesn't support firstBaseline/ lastBaseline, so this should not conform to SupportsBaselineLayoutProxy (super protocol).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be patching up that. Thanks @inamiy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, owningView has been implicitly tested. I think the missing test cases are for safeAreaLayoutGuide which I'll also add. I'll also rename LayoutElement to LayoutItem.

I'll try to figure out something about the IUOs.

@corujautx
Copy link
Contributor Author

corujautx commented Oct 17, 2017

@inamiy I added the requested fixes and I also think this PR also deprecates #267 as it also solves that problem.

ps: #265 is also a duplicate of this

@corujautx corujautx closed this Oct 17, 2017
@corujautx corujautx reopened this Oct 17, 2017
Copy link

@inamiy inamiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think overall now LGTM 👍

Since this is quite a big breaking change, I think #267 can be merged first for soft-landing ver 2.1, and then this PR for ver 3.0 (should have more reviewers).

@orta What do you think?

@orta
Copy link
Collaborator

orta commented Oct 18, 2017

OK, so this will need a bit of a rebase - I've released #267 as a 2.1.0 release

The idea behind this all seems 👍 to me, I'm happy to 3.0 it

@corujautx
Copy link
Contributor Author

Okay, I merged the current master with my fork. Please check if everything is okay and we can merge and release Cartography 3.0 :)

@corujautx
Copy link
Contributor Author

Can you check it @orta , @inamiy ?

@orta
Copy link
Collaborator

orta commented Oct 24, 2017

Oh yeah, my bad 👍

@orta
Copy link
Collaborator

orta commented Oct 24, 2017

I'll give this a one over

@orta
Copy link
Collaborator

orta commented Oct 24, 2017

Ah yeah, can I get an example of how to migrate from 2.x to 3.0? To add to the release notes. Then this is good to go from my side 👍

@corujautx
Copy link
Contributor Author

I posted above but:

The properties topLayoutGuideCartography and bottomLayoutGuideCartography have been replaced by car_topLayoutGuide and car_bottomLayoutGuide respectively. Those now are to be injected in constrain instead of the block that is supplied to the method.

Code that today looks like this:

constrain(view) { view in 
    view.top == self.topLayoutGuideCartography
    view.bottom == self.bottomLayoutGuideCartography
}

now will look like this:

constrain(view, self.car_topLayoutGuide, self.car_bottomLayoutGuide) { view, topLayoutGuide, bottomLayoutGuide in
    view.top == topLayoutGuide.bottom
    view.bottom == bottomLayoutGuide.top 
}

@@ -14,8 +14,6 @@ import Foundation

extension UIView: LayoutItem {
public func asProxy(context: Context) -> ViewProxy {
self.translatesAutoresizingMaskIntoConstraints = false
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 767821e...da5e483, why did translatesAutoresizingMaskIntoConstraints = false move to other methods?
It seems scattered than before 😕

Copy link
Contributor Author

@corujautx corujautx Oct 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sorry about that.

Here's the problem tho: this introduces a bug/breaking change on the library depending on how people use it, and I kinda discovered that by pointing out my fork to a project we are developing at our company.

When I centralized the translatesAutoresizingMaskIntoConstraints in the proxy generation step I was thinking "Hey this is going to make the library a lot more simple", but the issue is that lot of people use the syntax as in:

constrain(view, superview) { view, superview in ... }

instead of:

constrain(view) { view in 
    view.top == view.superview!.top
}

in order to avoid IUOs

When we moved to the proxy code, the first case generated a side effect of setting up translatesAutoresizingMaskIntoConstraints in the superview, and this broke the layout of several root views in UINavigationController or any container view controller.

I couldn't predict this side effect being generated by the code I made, so I changed back into the original behavior of the library, even if it looks ugly, to avoid more breaking changes.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explanation!
So, translatesAutoresizingMaskIntoConstraints = false impl is actually back to the original code now 🙄

@corujautx
Copy link
Contributor Author

@orta I posted the explanation above, please check.

@orta
Copy link
Collaborator

orta commented Oct 31, 2017

Ah yeah, thanks, OK, let's get the 3.0 train going

@orta orta merged commit bc78595 into robb:master Oct 31, 2017
@inamiy inamiy mentioned this pull request Nov 9, 2017
@ghost
Copy link

ghost commented Nov 11, 2017

There is an issue with this if we are adding more than five views we have to use let views:[LayoutItem] = [view1,view2,self.car_topLayoutGuide, self.car_bottomLayoutGuide ]

error
"Protocol 'LayoutItem' can only be used as a generic constraint because it has Self or associated type requirements"

@corujautx
Copy link
Contributor Author

corujautx commented Nov 11, 2017

@pprevalon That is why I added more overloads to constrain to support more than 5 elements (now it supports up to 10 elements).

The reason why this happens is because Swift doesn't allow us to use covariant generics, but instead all are invariant, making the array conversion a problem. e.g. the signature is func constrain<T: LayoutElement>(elements: [T], block: ([T.LayoutProxy]) -> Void) which basically means that if you use an array of View it'll generate a constrain method that takes necessarily a block that uses [ViewProxy] instead. Perhaps I could use LayoutProxy instead, but I feel that'd be more of a hassle than a solution, given that it'll create a casting hell and messes with type-safety.

If anyone has any solution to this problem, or a way to work around it, it'd be nice to hear, but I can't think of a way in Swift to create a working interface for that.

@NachoSoto
Copy link
Contributor

I'm confused about this change. With #267 we could do view.edges == view.superview!.safeArea.edges. That doesn't seem possible anymore?

@NachoSoto
Copy link
Contributor

Never mind, I'm silly, view.edges == superview.safeAreaLayoutGuide.edges works great :)

This is actually what I'm doing:

if #available(iOS 11.0, *) {
    constrain(self.scrollView, self) { scroll, superview in
        scroll.edges == superview.safeAreaLayoutGuide.edges
    }
} else {
    constrain(self.scrollView, self) { scroll, superview in
        scroll.edges == superview.edges
    }
}

This happens to be enough in my case cause this view isn't affected by top or bottom layout guides (it's in the middle of the screen).

@corujautx
Copy link
Contributor Author

@NachoSoto if you need to use top layout guide and bottom layout guide you could use:

constrain(self.scrollView, self.car_topLayouGuide, self.car_bottomLayoutGuide) { scroll, topGuide, bottomGuide in 
    scroll.top == topGuide.bottom
    scroll.bottom == bottomGuide.top
    scroll.leading == scroll.superview!.leading
    scroll.trailing == scroll.superview!.trailing
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants