WIP: Improve the Objective-C and Swift bridge #289
Reference in New Issue
Block a user
No description provided.
Delete Branch "improve-objc-swift"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What I've done
Here is what I've done so far:
YG
toYK
to support custom enumsCount
itemsLeftToRight
andRightToLeft
:-) we don't mind being verbose if it makes identifiers clearerFor example, here are what the Objective-C and Swift sample code look like:
Objective-C
Swift
Here's what I'd still like to do, but not sure on how best to achieve:
YKEdge based style values
I have purposefully left the style values which depend on an
YKEdge
alone because I'd like to improve them to be properties, but I'm undecided on how to. Here are my two solutions:Normalization
We could create
yk_positionLeft
,yk_positionRight
, etc... properties. The pro is that it's simple and efficient, but it's a bit messy.Sub-types
Or we could go through sub-types with
left
, `right, etc.. properties:Getting and setting such a property would look like this:
This would be the cleanest solution. But it forces us to initialise three extra objects per view. We could however restrict this solution to Swift and use a struct, which would reduce the performance cost.
Prefix in Swift
I removed the
yk_
prefix in Swift because its an anti-pattern and replaced it withlayout
. The first question is: would it be better if it wereyoga
instead?Whatever it is, I think we still need some kind of prefix to make sure there is no ambiguity that values like
width
andheight
are for layout calculations and not values ofUIView
derived fromframe
. But I'm not 100% happy of the solution. Like for the edge values, how about a sub-type?@hartbit updated the pull request - view changes
Haven't tried it yet, but it looks cool!
As far as the swift prefix goes I am in favor of
yogaWidth
style simply because Xcode autocompletion will immediately pick it up upon typingwidth
for example. Otherwise you would have to type.yoga.
before the autocomplete kicks in.I am in favor of YKEdge property names like
yogaPositionLeft
mainly for the same reason.Should your SwiftViewController class be named
YogaViewController
to indicate its function/purpose?Part of me wants to agree with you. But another part of me feels like this would feel foreign. For example, if we follow your argument to the end, we wouldn't have
view.bounds.size.width
butview.boundsSizeWidth
.Good point. Maybe they just need to make autocomplete better :-)
Concerning this, I don't mind changing it. But then I don't see why the Objective-C class should keep it's
ViewController
name. They are both yoga :)Correct again. I had not see it in context with the objc class. The name just seemed generic when I saw it in the commit. It's fine as is.
The more I think about it, the more I converge to this solution:
YGNodeBridge
class public and rename it toYKLayout
layout
yk_
prefixes on properties and methodsposition
,margin
, andpadding
valuesThis allows us to "namespace" all properties and methods so they are not confused with
UIView
properties (width and height). Because we've removed the ambiguity, we can safely remove theyk_
prefix.The result would look like this:
Objective-C
Swift
Post Scriptum
In my example, I renamed
usesYoga
toenabled
. It makes more sense once on theYKLayout
class to go for a more straight-forward property name.I think it would be best to keep
layout
as a property name. I can see this library be used as a dependency in other libraries to React-ify iOS and mentioningyoga
could confuse users who are not directly depending onyoga
.So the edge values would look like
root.layout.margin.bottom = 20
then.Looks clean. I like it. I like
layout
better thanyoga
as well.No, as you can see in the example, I decided to go for
root.layout.marginBottom
instead. But I'm not completely decided yet. Let's wait to see what @emilsjolander has to say :)I didn't see an example of a coded edge value in your example so I was just trying to clarify.
Can you see the
root.layout.paddingLeft = 5
androot.layout.marginAll = 10
lines in my example? I updated some parts of the code after submitting the comment. Perhaps you are seeing an old version.That explains it because there was definitely not an example of an edge value there.
Side note:
YGUndefined
(NAN) would be better expressed as optional values in Swift. But changing that would require us to redefine all value properties in Swift like this:@hartbit updated the pull request - view changes
Ok, I implemented what I presented previous, except for the optional values in Swift.
This is cool! Thanks 👍 Couple issues with the API before I start reviewing the code.
marginBottom
vsmargin.bottom
. I prefermargin.bottom
as I imagine this will lead to less code but have no strong preference.layout
toUIView
can be confusing for 2 reasons.yoga
. If another library wants to hide this behind the namelayout
that is possible for them to do if they choose to.style
andlayout
properties.layout
properties are the outputs of the calculation (what is stores in theUIView
frame). There is already out such layout output that cannot be stored in the frame (computed layout direction) and there could potentially be more in the future. Also keeping naming consistent with the C implementation here helps understanding.YG
.layout
of my components makes sense, but theyoga
of my components feels weird. Moreover, the property can't be hidden from a Swift transitent dependency: there is no way I know to import a module as a private dependency. Perhaps you can correct me on that.yoga
, I will oblige :)Why can't they use the same definitions? I feel strongly about keeping the same prefix. Let's find a solution for this.
It cannot be hidden, wrong wording, sorry :) What i meant was that ReactSwift could expose the property as
layout
via an alias if it wanted to. Or it could just make it clear that it uses yoga for layout and then it would not be weird.layout
on UIView also has a way to high chance of being used by apple in the future so let's avoid it.Fair point. But we may add more layout outputs in the future and then it could potentially become confusing.
I didn't want to use the same definitions to remove the
Count
item. It really feels odd to me to leak an implementation detail into the bridges. Ok, here's a potential solution: why don't be modify theenums.py
to removeCount
from the C enums and redefine it as a constant in a private header?Okay, let's go for
yoga
:)This sounds great! Could you do that as a separate PR?
@hartbit updated the pull request - view changes
@emilsjolander Done: #292
@hartbit This look like we can merge it very soon 👍 Thanks for all the work
layout
->yoga
apply
->applyLayout
(more explicit now that the prop is no longer calledlayout
)YogaKit/UIView+YogaKit.{h,m}
as a move fromYogaKit/UIView+Yoga.{h,m}
@emilsjolander There's still the question of edge values. Currently, I have them as
marginBottom
, notmargin.bottom
. If what you are worried of is duplication of code, that's not a problem, because I used macros (like in the C implementation). I think it's more a question of style.Ah yes I forgot about that one. I prefer
margin.bottom
because that would allow margin/border/padding to share a data structure which would allow for writing more generic code which could take this structure as a parameter.@emilsjolander Ok, I just had a discussion with a colleague at work that has done a fair amount of ReactNative and he'd propose following their syntax:
https://facebook.github.io/react-native/docs/layout-props.html
That would mean:
margin
instead ofmarginAll
marginLeft
, etc...Is that argument worth thinking about, or do we really want a separate data structure?
@hartbit I'm Ok with that, I don't feel strongly about this.
@emilsjolander I'll have the modifications done this evening. Thanks for the feedback!
@hartbit Awesome. I'm trying to merge #292 as we speak
Hey @hartbit! Thank you so much for all your work on this!
I would love to start merging some of these changes in, but I was wondering, would it be possible to start breaking them up into smaller pull requests? I think some of these features we can ship right away (
e170dde
,f24baff
), and it will the review of the other commits much easier.Unfortunately, the tool we use review/merge changes would bundle all these commits together into a massive one, and I rather break it up it into pieces :)
The code is looking really good! It gave me a thought though...
This feels like this is turning into LayoutKit. If the
YKLayout
is going to hang on to view internally to apply the layout to it...it feels like we are a step away from:UIView
category.YKLayout
so that we can subclass it and have it pumped out configured views.YKLayout
s to create view hierarchies.And this is almost exactly what
LayoutKit
does...Just playing devil's advocate here, but, in the meantime I think we should push forward!
@@ -0,0 +40,4 @@
@property (nonatomic) CGFloat positionBottom;
@property (nonatomic) CGFloat positionStart;
@property (nonatomic) CGFloat positionEnd;
@property (nonatomic) CGFloat positionHorizontal;
Personally, I find this makes the API much more confusing. There are multiple properties that essentially do the same thing. I could set
positionLeft
andpositionRight
or just simply setpositionHorizontal
. To me, it would be much more idiomatic to Objective-C and Swift if we did something like this.or
where YKEdges is an NSOption.
I would apply this comment to
margin
andpadding
too. cc: @emilsjolander@@ -0,0 +58,4 @@
@property (nonatomic) CGFloat paddingTop;
@property (nonatomic) CGFloat paddingRight;
@property (nonatomic) CGFloat paddingBottom;
@property (nonatomic) CGFloat paddingStart;
nit across the board, can we explicity add all the properties? i.e.
@@ -0,0 +40,4 @@
@property (nonatomic) CGFloat positionBottom;
@property (nonatomic) CGFloat positionStart;
@property (nonatomic) CGFloat positionEnd;
@property (nonatomic) CGFloat positionHorizontal;
@dshahidehpour I agree the solution I have is not ideal, but I fail to see how
UIEdgeInsets
or aNS_OPTIONS YKEdges
would allow us to express theStart
andEnd
values.@@ -0,0 +58,4 @@
@property (nonatomic) CGFloat paddingTop;
@property (nonatomic) CGFloat paddingRight;
@property (nonatomic) CGFloat paddingBottom;
@property (nonatomic) CGFloat paddingStart;
Sure!
@dshahidehpour I've opened a pull request for the gitignore fix (#294). I've having a few issues concerning the Carthage PR but I'll open something soon.
@@ -0,0 +40,4 @@
@property (nonatomic) CGFloat positionBottom;
@property (nonatomic) CGFloat positionStart;
@property (nonatomic) CGFloat positionEnd;
@property (nonatomic) CGFloat positionHorizontal;
@hartbit Whoops, I didn't even notice those, do we think they need to part of
YogaKit
? I purposely left some properties out at first because I didn't see a huge use-case for them with UIKit.@@ -0,0 +40,4 @@
@property (nonatomic) CGFloat positionBottom;
@property (nonatomic) CGFloat positionStart;
@property (nonatomic) CGFloat positionEnd;
@property (nonatomic) CGFloat positionHorizontal;
@dshahidehpour The Yoga website says: "We believe that Right-to-Left (RTL) should be a first class citizen when it comes to layout" and I strongly agree. UIKit also supports it by default with leading and trailing constraints in Auto Layout and I'd be very sorry to have them out of YogaKit.
You mention that there are multiple properties that do the same thing. But it's not unheard of in Cocoa. For example, a UIView's
frame.size
is the same as itsbounds.size
. Setting one affects the other. And it's the same with it'scenter
, which is the mid-x and mid-y offrame
.I know, this is a difficult design decision. But I think there is no perfect solution. For sake of discussion, here are the solutions I see, as well as their pros and cons.
Properties
Usage in Objective-C
Usage in Swift
Pros
Cons
Methods with
NS_ENUM
Usage in Objective-C
Usage in Swift
Pros
Cons
YGEdgeValue
Usage in Objective-C
Usage in Swift
Pros and Cons
This is basically a different flavor of properties. I'm not a hug fan of it because it multiplies the number of objects on the heap that yoga generates by 4 for a small gain in readability and API surface and a small loss in usage typing speed because having to go through two auto-completion hints (
padding
, thentop
).Conclusion
On and all, here's my ranking of preference:
@@ -0,0 +40,4 @@
@property (nonatomic) CGFloat positionBottom;
@property (nonatomic) CGFloat positionStart;
@property (nonatomic) CGFloat positionEnd;
@property (nonatomic) CGFloat positionHorizontal;
As an additional support for the properties, I don't think it would surprise UIKit users because it is fairly idiomatic of the AutoLayout APIs:
Add to that the fact that CSS and React Native users would feel right a home.
@hartbit I talked to @dshahidehpour offline, we are both super excited and happy with the direction of this.
We preferYGEdgeValue
approach as is makes the header for YogaKit much easier to understand as there will be a lot less properties to parse.So what we see as left to do here is:
layout
propertyyoga
apply
toapplyLayout
YK
prefix back toYG
@property (nonatomic, readwrite, assign) ...
@@ -0,0 +40,4 @@
@property (nonatomic) CGFloat positionBottom;
@property (nonatomic) CGFloat positionStart;
@property (nonatomic) CGFloat positionEnd;
@property (nonatomic) CGFloat positionHorizontal;
Hey @hartbit, sorry I took so long to respond. I really appreciate the time you put into that response, and I support all of it 100%.
@emilsjolander I know we talked about
YGEdgeValue
, but, I think @hartbit makes a compelling case for why we should put these properties on the layout object.@hartbit Thanks for being patient! I've talked @emilsjolander and we've decided to follow your argument and not use
YGEdgeValue
. I'm removed it from the list of requested fixes above, thanks!@emilsjolander @dshahidehpour I started by creating a pull request for an Xcode framework target to get it working with Carthage. Unfortunately, I've hit a few road blocks when doing that. Care to have a look? #305
By the way, while looking at the changes necessary, I saw that the
yoga
property still points to aYGLayout
instance. For the same reasons that you did not like thelayout
name, I should probably rename that class toYGYoga
. But while thinking about it, I thought: why not rename both tostyle
andYGStyle
?@hartbit I'll be back online tomorrow, and I'll be able to take a look!
Scratch my last message. The fact that the object also has "layout" properties like
instrinsictSize
makes me reconsider my thought. So, should we rename it toYGYoga
? Sounds a bit weird.@hartbit I think
YGLayout
is fine. The reason i was against naming the propertylayout
is the same reason I would be againststyle
, they are very generic name for properties in a UIView category. Let's stick withyoga
as the property name andYGLayout
as the class name for now. This might change later but I would rather get this merged and then continue iterating on it.@@ -0,0 +40,4 @@
@property (nonatomic) CGFloat positionBottom;
@property (nonatomic) CGFloat positionStart;
@property (nonatomic) CGFloat positionEnd;
@property (nonatomic) CGFloat positionHorizontal;
I like the YGEdgeValue the best, but why does it need to be a full class? In C/C++, the pattern would be lightweight structs for this kind of data set. That way I can do something like:
view.layout.padding = {.top = 10, .bottom = 10}
Or a hypothetical
view.layout.padding = YGCreateEdgeValues(...);
@emilsjolander @dshahidehpour what was the reason for rejecting YGEdgeValue? If it's represented as a lightweight struct, I think that's more idiomatic UIKit.
I commented above not seeing your strikethrough, but I'll repost it here as I feel strongly this is very easy to read and you can pass the struct around.
view.layout.padding = {.top = 10, .bottom = 10}
or even create once and set to multiple views instead of setting properties.
@eklipse2k8 We mentioned the reasons we preferred to not use it above:
I went back and forth on this myself. I think what's best is for us to fixup/land this revision, and then iterate on refinements to the API afterwards. I'd be happy to talk to you more about this offline.
I understand what you mean. And I'm totally open to discussing it. But some problems remain:
It's not 100% idiomatic UIKit. Very little Objective-C code I've seen in the wild uses the
{.top = 10, .bottom = 10}
C syntax, and instead preferCGRectMake
,UIEdgeInsetsMake
and other function initialisers. I was actually reminded of that syntax while looking into the Yoga code: I had completely forgotten about it while working several years in Objective-C land.It does force an extra dot accessor when used from Swift, which does not have an equivalent C syntax:
By the way, I was on vacation until now, but I will be working on getting this PR back into shape very soon.
@hartbit What is the ETA on this? Would love to get this in before https://github.com/facebook/yoga/pull/278 and https://github.com/facebook/yoga/pull/280
Was planning on getting it done this evening.
I did it in a separate PR to make it easier for me to work from the latest commit of master: #322
Pull request closed