robb / cartography Goto Github PK
View Code? Open in Web Editor NEWA declarative Auto Layout DSL for Swift :iphone::triangular_ruler:
License: Other
A declarative Auto Layout DSL for Swift :iphone::triangular_ruler:
License: Other
layout(view) { view in
view.width == view.superview.width
view.height == view.superview.height
}
Here is a screen shot of my code.
http://wess.co/WHO4
With this offending snippet of code:
layout(crashingView) {(crashingView) in
crashingView.width == crashingView.superview!.width ~ 750
return
}
I get this assertion failure: Mutating a priority from required to not on an installed constraint (or vice-versa) is not supported. You passed priority 750 and the existing priority was 1000.
Unfortunately I couldn’t get this to reproduce with the test suite. :/
My working hypothesis is that Cartography is doing something like this:
let constraint = NSLayoutConstraint(item: crashingView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0)
NSLayoutConstraint.activateConstraints([constraint])
// Crashes here
constraint.priority = 750
Instead of doing the right thing, which is this:
let constraint = NSLayoutConstraint(item: crashingView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0)
// No crash.
constraint.priority = 750
NSLayoutConstraint.activateConstraints([constraint])
If you want the complete crash log it’s available here.
The entire ViewController that the offending snippet comes from is available here.
Thanks for providing such a nice library!
I love using overloaded operators for auto-layout. But using boolean operators like ==
to have side effects just seems too weird to me. How about making custom operators surrounded by vertical lines: |==|
,|<|
, etc? It would look just as clear and will be more obvious that some magic is taking place. Here is how a sample would look:
layout(view1, view2) { view1, view2 in
view1.width |==| (view.superview!.width - 50) * 0.5
view2.width |==| view1.width - 50
view1.height |==| 40
view2.height |==| view1.height
view1.centerX |==| view.superview!.centerX
view2.centerX |==| view1.centerX
view1.top |>=| view.superview!.top + 20
view2.top |==| view1.bottom + 20
}
Hey there,
I've been having some trouble building Cartography. I copied Cartography.xcproj
into my project and built, but keep seeing this sort of error:
<unknown>:0: error: /Users/steve/Library/Developer/Xcode/DerivedData/TestProject-ghllptdppatsujhcndvznuuepxlw/Build/Products/Debug-iphonesimulator/Cartography.framework/Modules/module.modulemap:2: umbrella header 'Cartography.h' not found
<unknown>:0: error: could not build Objective-C module 'Cartography'
<unknown>:0: error: /Users/steve/Library/Developer/Xcode/DerivedData/TestProject-ghllptdppatsujhcndvznuuepxlw/Build/Products/Debug-iphonesimulator/Cartography.framework/Modules/module.modulemap:2: umbrella header 'Cartography.h' not found
<unknown>:0: error: could not build Objective-C module 'Cartography'
<unknown>:0: error: could not build Objective-C module 'Cartography'
<unknown>:0: error: could not build Objective-C module 'Cartography'
Any ideas as to what I'm doing wrong?
Thanks in advance!
Hey there,
Is it possible to replace an existing constraint? I need to do this for animation.
layout(view) { (view) in
//...
view.left == view.superview!.left // needs to be removed so I can change the position of the view
}
I'm trying to do this type of thing
layout(splitView, boxStatus) { split, box in
box.height == 31
split.height == box.superview!.height - 31
split.bottom == box.top
}
which works, but I'd like to parameterize this so the 31 is a variable. (I'm not even able to use box.height
in the second line.)
Hints, please, or is this a limitation of the framework?
Thanks!
When trying to pin a view to the left, right, bottom, and top in a layout block using the subtraction operator or the right or bottom (so the view is X pixels from the right side of it's superview) will cause a random constraint to be added. This only appears to manifest itself when it's a view inside another autolayout view. The constraint that gets added sets the superview to a width or height of zero which makes the constraints unsatisfiable.
Workaround: Order off adding the constraints appears to matter. If view A is put inside view B, then if view B has it's constraints added before A's then the phantom constraint isn't added. In practice this workaround is pretty clunky. Adding the same constraints using the NSLayoutConstraint methods doesn't exhibit this behavior.
I put together a sample project to recreate the issue https://github.com/brandonroth/potential-octo-meme.git
One of the features I miss from masonry is the ability to remake constraints. It would be very nice to have in this library!
It looks like there are many compiler errors due to the changes in Xcode 6 Beta 4. It looks like some of the errors can be fixed by declaring classes/structs/functions/methods/properties/protocols/enums/etc public. The errors I'm not sure about say things like, "Size is not convertible to 'MirrorDisposition'", or "Size is not convertible to UInt8".
Xcode 6 Beta 3 came out today and includes changes to Swift. It looks like Cartography will need to be modified for syntax changes with arrays and the half range operator, as well as changes to nil.
Would it be possible to add support for dictionaries when passing an arbitrary number of views? Having the ability to create an array of views is great, but accessing the views using just an array index (e.g. views[0] and views[1]) makes the code a bit more unreadable. If we could access the views using a dictionary like this:
layout(["view1": view1, "view2": view2) { (views) -> () in
views["view1"].width == (views["view1"])].superview!.width - 40)
views["view1"].height == 50
}
It would make for more readable code that would be easier to maintain.
I dont know how to describe this issue: Simply try run this code:
import Foundation
import UIKit
public class SettingsViewController: UIViewController {
let signoutButton = UIButton()
let copyrightTextView = UITextView()
public override func loadView() {
super.loadView()
signoutButton.setTitle(NSLocalizedString("Odhlásit", comment: ""), forState: .Normal)
view.addSubview(signoutButton)
copyrightTextView.textAlignment = .Center
copyrightTextView.editable = false
copyrightTextView.text = NSLocalizedString("Copyright 2015 Springtide Ventures s.r.o.", comment: "")
copyrightTextView.scrollEnabled = false
copyrightTextView.backgroundColor = UIColor.clearColor()
copyrightTextView.textColor = UIColor.blackColor()
view.addSubview(copyrightTextView)
let height = copyrightTextView.sizeThatFits(CGSize(width: view.bounds.width, height: CGFloat.max)).height
constrain(copyrightTextView, signoutButton) { c, l in
l.centerX == l.superview!.centerX
l.width == l.superview!.width
l.top == 100
c.top == l.bottom + 20
c.centerX == l.centerX
c.height == Float(height)
}
}
}
I tried
self.button1.backgroundColor = UIColor.redColor()
layout(self.button1) { view in
view.left == view.superview!.width * 0.1
view.width == view.superview!.width * 0.1
view.top == view.superview!.height * 0.1
view.height == view.superview!.height * 0.3
}
but assigning left
and top
like that does not work. Shouldn't it?
Using a fix margin like this does work for the positioning:
self.button1.backgroundColor = UIColor.redColor()
layout(self.button1) { view in
view.left == view.superview!.left + 20
view.width == view.superview!.width * 0.1
view.top == view.superview!.top + 20
view.height == view.superview!.height * 0.3
}
But it seems like it's ignoring the width and height and is still using the size given in IB.
I haven't dug deeper yet.
This is a major issue, considering only about two-thirds of the users out there are using iOS 8.
Example of an NSView Category:
extension NSView {
func sizeToFillSuperview() {
layout(self) { view in
view.size == view.superview!.size; return
}
}
}
Swift Compiler Error is "extra argument in call"
Early substitution for self doesn't help, nor does anything else I've thought of.
Obviously a global function like this is okay, but not ideal:
func sizeToFillSuperview(view: NSView) {
layout(view) { box in
box.size == box.superview!.size; return
}
}
THANKS!
I have function, call it f, which returns a CGFloat, and I want to do this:
layout(view) {
view.width == f(); return
}
This won't work currently
The code
layout(view) { view in
view.centerY == view.superview!.height * 0.3; return
}
doesn't work, but it does work if you replace "centerY" with "width".
Let's imagine I create a ConstraintGroup let group = constrain( ... ) { ... }
and I later want to remove all those constraints I added (but I don't want to replace them with anything else).
Is there an easy way to remove all constraints of a group?
Right now, the only thing I'm thinking of is constrain(someRandomView, replace: group) { v in return }
, but that's not very elegant, and so not on par with the rest of Cartography :)
Hi, I'm building an app with deployment target 7.1. Therefore, I also set the target to 7.1 also for Cartography. When building, I get the following warning: Embedded dylibs/frameworks only run on iOS 8 or later
On the simulator, the app seems to run fine under iOS7.1 however, I cannot test it on iOS7 due to lack of devices.
Hi, I just found this project today and would like to try it out. When I open the project in Xcode 6 Beta 2 and try to build it, I get the following error:
Property.swift - Swift Compiler Error - Could not find member 'view'
The error points to line 57 of Property.swift where the NSLayoutConstraint is being created in the apply method.
Changing the multiplier and constant parameters to CGFloat fixes the issue for me. This changes the apply method to look like the following (also, all tests pass with this change):
func apply(from: Property, coefficients: Coefficients = Coefficients(), to: Property? = nil, relation: NSLayoutRelation = NSLayoutRelation.Equal) -> NSLayoutConstraint {
from.view.setTranslatesAutoresizingMaskIntoConstraints(false)
let superview = commonSuperview(from.view, to?.view)
var toAttribute: NSLayoutAttribute! = NSLayoutAttribute.NotAnAttribute
if to != nil { toAttribute = to!.attribute }
let constraint = NSLayoutConstraint(item: from.view,
attribute: from.attribute,
relatedBy: relation,
toItem: to?.view,
attribute: toAttribute,
multiplier: CGFloat(coefficients.multiplier),
constant: CGFloat(coefficients.constant))
superview?.addConstraint(constraint)
return constraint
}
If I have a few views that are all going to have the same property, for example
button1.top == superview.top
button2.top == superview.top
It would be great to be able to chain those operations and say
button1.top == button2.top == superview.top
Currently it produces an error Non-associative operator is adjacent to operator of same precedence
. I'm not sure if this is possible with Swift, but it would help me make the code a bit more streamlined.
Maybe worth some logging or an assert, currently if superview is nil the constraint is ignored
let superview = commonSuperview(from.view, to?.view)
at line: https://github.com/robb/Cartography/blob/master/Cartography/Property.swift#L23
ran into this by adding the child view to self.contentView
but attaching constraints to self
self.avatarSelectionView = ...
self.contentView.addSubview(self.avatarSelectionView!)
layout(self,avatarSelectionView!) { superview, avatarSelectionView in
avatarSelectionView.edges == superview.edges ; return
}
Is there a way to capture an Edge for use in another layout() call at a later time? I'm trying to do something like the following, but get a crash when I unwrap viewBottom.
var viewBottom: Edge?
layout(self.view) { view in
view.height == ...
view.top == ...
viewBottom = view.bottom
}
layout(self.view2) { view2 in
view2.top == viewBottom! + 10
}
So many hours saved
Hey there, I'm curious to know if Cartography will accomplish my needs. I'd like to have a list of stacked UIView's that fill the width of the screen, each having a dynamic height, and each being separated by 10 points apart from each other. AKA, something like Facebook's News Feed. Thanks!
Cartography is the first third party framework I've tried using in my Swift project. I'm having difficulty with the installation process. So far I have:
Cartography.framework
bundle in the uncharted depths of my filesystemCartography.framework
bundle into my projectCartography.framework
to the "Link Binary With Libraries" build phase of my targetimport Cartography
to the top of my Swift file where I wanted to use itNow I'm getting the error Use of unresolved identifier 'layout'
and I'm fresh out of ideas.
(It would be helpful to have the basic process in the README.md. I know most of it isn't specific to Cartography, but the procedures around this are so new and rapidly evolving it'd be helpful to have the basic known-working steps here.)
That time again... looks like infix is now a modifier and no longer an attribute
I am not sure why but according to the Swift compiler CGFloat
is an undeclared type - and I had to add import UIKit
to that file to make it compile.
https://github.com/robb/Cartography/blob/master/Cartography/Extensions.swift#L37
I am bit flabbergasted but it seems like it's not declared in Foundation. http://stackoverflow.com/questions/24700245/cgfloat-not-defined-in-foundation-framework
I don't fully grok the implications of extending framework classes in Swift yet, but being able to directly write
let view1 = UIView()
let view2 = UIView()
view1.width == 200
view1.height == view1.width
view2.width == view1.width
view2.height == view2.width
seems more natural. Not sure if it's worth the risk of collisions though…
Could you write a sample app in a similar vein that the FLKAutoLayout project did?
enum NSLayoutAttribute : Int {
// ...
case FirstBaseline
case LeftMargin
case RightMargin
case TopMargin
case BottomMargin
case LeadingMargin
case TrailingMargin
case CenterXWithinMargins
case CenterYWithinMargins
}
Trying to learn Cartography but ran into the problem below at my first attempt.
layout(profileImageView) { view in
view.left == 8
}
Compiler says: Could not find member "left"
I have most if my UIViews done in code, composed of UIKit built-in UIViews. I started using Cartography just yesterday and its amazing how simple it is to setup constraints using Cartography, and its easily readable!
However I encountered problem with layout
function, it calls - (void)layoutIfNeeded
every time it finishes up the block that composes constraints and adds them to proper UIView. Since I have code like below in my view and its superview (the layoutViews
function is called in initializer) this results in many Unable to simultaneously satisfy constraints
during runtime and the time it takes to setup the view is much higher and is unusable for UITableViewCell
s. I tried commenting call of car_updateAutoLayoutConstraints()
in Layout.swift and it helped, time to load the view reduced.
private func layoutViews() {
self.addSubview(self.titleBackgroundView)
self.titleBackgroundView.addSubview(self.titleLabel)
self.addSubview(self.backgroundImageView)
self.addSubview(self.shareButton)
self.shareButton.addSubview(self.shareIconImageView)
self.shareButton.addSubview(self.shareLabel)
self.addSubview(self.potentialWinBackgroundView)
self.potentialWinBackgroundView.addSubview(self.amountTitleLabel)
self.potentialWinBackgroundView.addSubview(self.amountLabel)
self.addSubview(self.betView)
self.betView.addSubview(self.betButton)
self.betView.addSubview(self.didBetView)
self.didBetView.addSubview(self.checkmarkView)
self.didBetView.addSubview(self.didBetLabel)
self.addSubview(self.wonView)
self.wonView.addSubview(self.wonLabel)
self.addSubview(self.lostView)
self.lostView.addSubview(self.lostLabel)
self.addSubview(self.peopleContainerView)
self.peopleContainerView.addSubview(self.friendsCollectionView)
self.peopleContainerView.addSubview(self.peopleCountLabel)
// title bg
layout(self.titleBackgroundView) { (titleBg) in
titleBg.top == titleBg.superview!.top
titleBg.leading == titleBg.superview!.leading
titleBg.trailing == titleBg.superview!.trailing
titleBg.height == 48
}
// title
layout(self.titleLabel) { (title) in
title.leading == title.superview!.leading + 20
title.trailing == title.superview!.trailing - 20
title.top == title.superview!.top
title.bottom == title.superview!.bottom
}
// background image
layout(self.backgroundImageView, self.titleBackgroundView, self.potentialWinBackgroundView) { (img, titleBg, potentialBg) in
img.leading == img.superview!.leading
img.trailing == img.superview!.trailing
img.top == titleBg.bottom
img.bottom == potentialBg.bottom
}
// potential bg
layout(self.potentialWinBackgroundView, self.peopleContainerView) { (potentialBg, peopleContainer) in
potentialBg.leading == potentialBg.superview!.leading
potentialBg.trailing == potentialBg.superview!.trailing
potentialBg.bottom == peopleContainer.top
potentialBg.height == 48
}
// amount title
layout(self.amountTitleLabel) { (amountTitle) in
amountTitle.centerY == amountTitle.superview!.centerY
return
}
// amount
layout(self.amountLabel, self.amountTitleLabel) { (amount, amountTitle) in
amount.baseline == amountTitle.baseline
return
}
// bet
layout(self.betView, self.potentialWinBackgroundView) { (bet, potentialBg) in
bet.bottom == potentialBg.top - 16
bet.centerX == bet.superview!.centerX
bet.width == 75
bet.height == 75
}
// bet button
layout(self.betButton) { (betButton) in
betButton.edges == betButton.superview!.edges
return
}
// did bet
layout(self.didBetView) { (didBet) in
didBet.edges == didBet.superview!.edges
return
}
// did bet checkmark
layout(self.checkmarkView) { (checkmark) in
checkmark.top == checkmark.superview!.top + 15
checkmark.centerX == checkmark.superview!.centerX
checkmark.width == 28
checkmark.height == 25
}
// did bet title
layout(self.didBetLabel, self.checkmarkView) { (didBetTitle, checkmark) in
didBetTitle.top == checkmark.bottom
didBetTitle.leading == didBetTitle.superview!.leading
didBetTitle.trailing == didBetTitle.superview!.trailing
}
// share
layout(self.shareButton, self.titleBackgroundView) { (share, titleBg) in
share.leading == share.superview!.leading + 12
share.trailing == share.superview!.trailing - 12
share.top == titleBg.bottom
share.height == 44
}
// share icon
layout(self.shareIconImageView) { (shareIcon) in
shareIcon.leading == shareIcon.superview!.leading + 20
shareIcon.centerY == shareIcon.superview!.centerY
shareIcon.height == 32
shareIcon.width == 32
}
// share title
layout(self.shareLabel, self.shareIconImageView) { (shareTitle, shareIcon) in
shareTitle.leading == shareIcon.trailing + 5
shareTitle.centerY == shareIcon.centerY
}
// won
layout(self.wonView, self.potentialWinBackgroundView) { (won, potentialBg) in
won.bottom == potentialBg.top - 16
won.centerX == won.superview!.centerX
won.height == 75
won.width == 75
}
// won title
layout(self.wonLabel) { (wonTitle) in
wonTitle.edges == wonTitle.superview!.edges
return
}
// lost
layout(self.lostView, self.wonView) { (lost, won) in
lost.size == won.size
lost.bottom == won.bottom
lost.centerX == won.centerX
}
// lost title
layout(self.lostLabel) { (lostTitle) in
lostTitle.edges == lostTitle.superview!.edges
return
}
// people container
layout(self.peopleContainerView) { (peopleContainer) in
peopleContainer.bottom == peopleContainer.superview!.bottom
peopleContainer.leading == peopleContainer.superview!.leading
peopleContainer.trailing == peopleContainer.superview!.trailing
}
// people count
layout(self.peopleCountLabel) { (people) in
people.leading == people.superview!.leading + 8
people.trailing == people.superview!.trailing - 8
people.height == 13
}
// friends
layout(self.friendsCollectionView, self.peopleCountLabel) { (friends, people) in
friends.leading == friends.superview!.leading
friends.trailing == friends.superview!.trailing
friends.top == people.bottom + 2
}
}
I will try to resolve this later, I already have an idea about solution. Anyone can feel free to implement the solution.
Lets declare another layout
function with closure as parameter and declare some global or static property/variable of type Bool
. If you call layout { .. }
the function sets bool to false and executes the closure containing other layout
calls, the other layout
functions will check for bool, if its true it will call layoutIfNeeded
if not, lets skip it. After calling this closure it will set it back to true and calls layoutIfNeeded
Or am I wrong? Am I doing something wrong. All I am trying to avoid is calling layout
with array as parameter, well, for obvious reasons.
After thinking about how to best replace constraints in #26 and #55, I've come to understand that:
The latter requires some knowledge about which views the constraints will be attached to, which may not be obvious to casual readers of your code.
How about instead of having to capture constraints individually, we allow users to group them in semantic units, like so:
let fixed = constrain(avatar, description) { avatar, description in
avatar.top == avatar.superview!.top + 10
avatar.left == avatar.superview!.left + 10
avatar.right == avatar.superview!.right - 10
avatar.width == avatar.height
description.top == avatar.bottom + 10
description.width == avatar.width
description.centerX == avatar.centerX
}
var descriptionHeight = constrain(description) { description in
description.height == 30 // Roughly two lines of content
}
Then, when we want to show more of the description
view later
constrain(description, context: descriptionHeight) { description in
description.height >= description.superview!.bottom - 10
}
UIView.animateWithDuration(0.5, animations: view.layoutIfNeeded)
This will clear all constrains in the descriptionHeight
context, replace them
with description.height >= description.superview!.bottom - 10
and animate to
the new solution.
Context
may not be the best name. Other possiblities from the top of my head would be Group
, ConstraintGroup
or ConstraintSet
…
Seems like I run into these
CocoaPods/CocoaPods#2226
http://openradar.appspot.com/radar?id=5536341827780608
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool: unknown option character `X' in: -Xlinker
Thanks for Cartography! I use it with gusto on iOS and would like to use it in my Mac project, but can't yet because of this issue.
Create a fresh Swift Mac app project in Xcode 6.1.1.
In the nib, add an NSTableView to the app's window.
You can build and run now, if you like. The window looks as expected:
Build Cartography 0.2 using Carthage and link it. Build and run the app again. Now it looks like this:
Here's a project you can use to repro, with steps 1-3 as separate commits: https://github.com/brow/CartographyIssue
import UIKit
import Cartography
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let imageView = UIImageView()
let imageSize: CGFloat = 75.0
imageView.frame = CGRectMake(0, 0, imageSize, imageSize)
imageView.bounds = CGRectMake(0, 0, imageSize, imageSize)
println((self.view.bounds.size.width - imageSize) / 2)
println((self.view.bounds.size.width - imageSize) / 2 - 150)
imageView.layer.cornerRadius = CGRectGetHeight(imageView.bounds) / 2
imageView.backgroundColor = UIColor.clearColor()
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 1
imageView.layer.borderColor = UIColor.grayColor().CGColor
self.view.addSubview(imageView)
layout(imageView) { imageView in
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
layout(view1, view2, superview) { view1, view2, superview in
view1.size == view2.size
view1.center == superview.center
}
Hi,
Thanks for your elegant work.
It'll be much nicer if user can specify content compression resistance and hugging priority inside the block using elegant descriptive syntax rather than old objc way.
Hey there,
I was wondering if there's a quick snippet handy which could illustrate the usage of the layout method accepting an arbitrary array of views (https://github.com/robb/Cartography/blob/master/Cartography/Layout.swift#L32) as opposed to a fixed amount. I'm still new to swift so I'm having trouble understanding how to use this method.
Hello. Can we set constraint priorities with DSL?
There seems to be an implicit return in layout blocks containing only a single statement.
layout(view, superview) { view, superview in
view.top == superview.top + 100
}
throws over the compiler whereas
layout(view, superview) { view, superview in
view.top == superview.top + 100
return
}
doesn't. I guess the compiler treats the first block as (LayoutProxy, LayoutProxy) -> NSLayoutConstraint
, in which case overloading layout
should do the trick.
Let's use Carthage for those dependencies.
I'm trying to use a constant to specify how much of a margin i want to use.
let margin = 10.0
label.top == label.superview!.top + margin
I can't get this to work no matter how I cast margin. Am I missing something?
Maybe something using ~
?
(view1.width >= 100) ~ 100
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.