Closed jamonholmgren closed 11 years ago
I've been an iOS dev since 4.0, so I might be a little stuck in my ways, but personally I'm fine with the way things are working currently. The only thing that would make me even happier would be if the add()
method would also be defined on UIView
so that I could do things like:
class WeatherView < UIView
attr_accessor :conditions
def self.new(conditions=nil)
wv = alloc.initWithFrame([[0,0], [320,480]])
wv.conditions = conditions
wv
end
def initWithFrame(f)
super
add UILabel, font: :bold.uifont(12), backgroundColor: :clear.uicolor, placeholder: "Conditions"
self
end
end
This would also allow me to use add()
more robustly in my controller, since I could call it on self and add to the self.view
, but then I could also add items to existing views easily too:
class HomeScreen < PM::Screen
def view_did_load
@box = add UIView, frame: [[10,10],[40,40]], backgroundColor: :red.uicolor
@box.add UILabel, font: :bold.uifont(12), backgroundColor: :clear.uicolor, placeholder: "Conditions"
end
end
My rule of thumb is if I add more than 3-4 views in my controller, I need to create a view subclass & do my setup there. But it's so much easier to use the hash attributes syntax that add()
provides :)
Pixate looks awesome and seems to be best equipped for out-of-the-box good design (e.g. with their Bootstrap port).
Let's move add
into its own module and make it work with UIView subclasses, then. Work for you?
I agree, Alan. Pixate is definitely cool -- I was one of the Kickstarter supporters and have a T-shirt even. Haha...
Yah, Pixate is really cool. But I'm sure a lot of people don't want to pay for the license.
When I first checked out ProMotion this week (after working on many ios apps from first release of the sdk), I thought, yah, I like this approach of making things less obj-c (and I'm prob one of the few people who doesn't mind obj-c). Right now at my office we've fallen into the Storyboard trap - namely issues with merges all the time and not even sure you touched something which shows up as a regression in the build.
I've been thinking a lot about this problem for a few months and while we won't be switching our project over to Ruby Motion anytime soon, I still have a good sense of what I would like (in an ideal world). Maybe something between css and teacup..? (below doesn't really share much with css except in how properties could be considered for inclusion)
Stylescreen.new(:home) do
# this could live in a base style class
view do
backgroundColor: "#edebe6".uicolor
end
# as could this
button do
titleFont: fontWithName('Signika-Regular', size:20),
backgroundColor: "#edebe6".uicolor,
borderWidth: 1.0,
borderColor: "#e0ded9".uicolor,
cornerRadius: 5.0
end
submit_button :extends => :button, :instance => true do
left: 10,
right: 10,
bottom: constrain_above(:cancel_button)
end
cancel_button :extends => :button do
left: 10,
right: 10,
bottom: constrain_pin(:bottom)
end
end
class HomeScreen < PM::Screen
# Not even sure this would be needed.. or maybe HomeScreen < PM::StyledScreen
style :home
def on_load
# So we could hook up the events later
@cancel_button = from_style(:cancel_button)
@submit_button = from_style(:submit_button)
add [@cancel_button, @submit_button]
# or maybe by setting the :instance => true in the style you get a free @submit_button and
# you can avoid the above add to view code..? Just a thought. There's definitely reasons
# one might not want to do this, but this is ruby and i want to type less and get more :)
end
end
Obviously this is closely related to teacup, but to me, this feels much more of what I would want with a Ruby library. It also gives me the added power of defining what I need in the context of ProMotion but not having to try to hack in teacup. I would love to ditch the CGRectMake calls in my implementation classes and have it work in the style definitions with autolayout. (Autolayout support is a must have in my mind.)
It's probably a decent amount of work with constraints, the implicit alloc/inits, and needing to decide what styles can be applied to the views/controls and layers. Probably less work than creating something like pixate tho..
Anyway, I just thought I'd chime in and it could be something I could set aside some time to contribute.
Thanks for the input, Jason! Good stuff.
One thing I want is to have a common handler for conversion of camel case to underscores. We can do that with a monkeypatch of String like the ActiveSupport's Inflector class:
class String
def to_snakecase
self.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
def to_camelcase(uppercase_first_letter=true)
string = self.dup
if uppercase_first_letter
string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
else
string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
end
string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::')
end
end
This would allow us to handle ruby-style snake case and Obj-C's headless camel case for these attributes.
If you haven't caught on by now, nothing's really sacred with me as far as monkeypatching the crap out of RubyMotion. As long as it doesn't cause bugs or make a lot of gems incompatible I take a pretty pragmatic approach to this.
Just thinking out loud (figuratively) here...
ProMotion::Styles.define do
style login_button: {
title_font: fontWithName('Signika-Regular', size:20),
background_color: "#edebe6".uicolor,
border_width: 1.0,
border_color: "#e0ded9".uicolor,
corner_radius: 5.0
}
style profile_pic_view: {
custom_style: something,
layer: {
something_inside_layer: 5
}
}
end
class HomeScreen < ProMotion::Screen
def will_appear
add UIButton, style: :login_button, on_touch: :log_in_action
add CustomProfileView, style: :profile_pic_view, on_touch: :maximize_profile_action
end
# ...
end
Make add
have all of the proper initializers mapped to as many UIKit objects as we can think of so you can just pass in the class and get a blank instance out of it.
Maybe the on_touch
is going too far.
It might work better to make the style hashes into lambda blocks and not run them until called. I wouldn't want all that stuff sitting in memory.
Jamon,
I like that! Seems like a good direction to start with. I think you're right regarding the lambda blocks too. My one question would be what would make the most sense for breaking up the styles and then including them as needed. Not a hard problem to solve, but it would probable make sense from a maintenance perspective.
Jamon,
Your last proposition is very nice and I like the approch and the on_touch
possibility. It could be great to have the possibilty to define the style inside the class of the view or the controller to avoid multiple class definition in addition to this (and be more readable).
And you with that kind of code, you could style an element and have a another element in an other view or controller with the same "style class" and be different.
class HomeScreen < ProMotion::Screen
def will_appear
add UIButton, style: :login_button, on_touch: :log_in_action
end
def styling
style login_button: {
title_font: fontWithName('Signika-Regular', size:20),
background_color: "#edebe6".uicolor,
border_width: 1.0,
border_color: "#e0ded9".uicolor,
corner_radius: 5.0
}
end
end
Good point, @Swatto ! That would actually work really well. Although, is that really much different than defining a method that returns the styles?
class HomeScreen < ProMotion::Screen
def will_appear
add UIButton, style: login_button_style, on_touch: :log_in_action
end
def login_button_style
{
title_font: fontWithName('Signika-Regular', size:20),
background_color: "#edebe6".uicolor,
border_width: 1.0,
border_color: "#e0ded9".uicolor,
corner_radius: 5.0
}
end
end
I think this approch is less powerfull than the styling
method :
The def styling
with all of the definition in it, have a major feature : we can generate style with event (a return name from a server, from an input of the user, etc).
With the name of the styling in a method definition name, we can't generate style by his name.
What do you think about it ?
You could do:
method_name_here = :login_button_style
add UIButton, style: send(method_name_here), on_touch: :log_in_action
send
calls a method by a symbol or string name.
I do think this needs careful thought. We don't want to overcomplicate it.
The thing that I'm considering right now is lazy-loading the attributes hash. We don't want to hold all the attribute hashes in memory if they're not in use. A lambda or block would work but would be a bit awkward in all the scenarios I've imagined. Any thoughts?
I agree with Swatto, but not only for reasons listed, but also because of code separation and sharing.
Putting styles in a method of the (for instance) HomeScreen takes away all the power of this concept. Like css, a big part of the power of styles (for me anyway) is reuse and extensibility. I want one base button, for example, and then extend off that for multiple screens. And having this adhere to a file/naming convention, outside of implementation, would be really cool.
Agree, Jason. I'll do some thinking on this next week. Ideas welcome.
The thing that has me stuck on this one still is the memory usage. I don't want to store all the styles (and their associated objects) in memory in the chance that an element might use it. Even Teacup ends up instantiating a lot of styles without using them if you re-use stylesheets across ViewControllers.
It likely makes the most sense to create procs that only run when that style is called.
ProMotion::Styles.define do
style :login_button do {
background_color: UIColor.blackColor,
font: fontWithName('Signika-Regular', size:20)
} end
style :logout_button, inherit: [ :login_button, :base_styles ] do {
background_color: UIColor.redColor,
font: fontWithName('Signika-Bold', size:20)
} end
# or maybe...
style :logout_button do {
include: [ :login_button, :base_styles ],
background_color: UIColor.redColor,
font: fontWithName('Signika-Bold', size:20)
} end
end
The syntax looks slightly wonky, I realize, with the embedded hash, but this would be the most performant way to do this I believe -- a block (or proc) with a hash inside.
The inherit
thing seems like it would work pretty well. You just merge the hashes. This would be fairly straightforward to implement.
Check out: https://github.com/tombenner/nui
And see an implementation i did here: https://github.com/Skookum/RubyMotionTalk/tree/master/RedditRss
I don't think element styling should be a part of ProMotion
when there are so many other libraries that do it so well.
That looks really good. On your RedditRss app, I didn't see your stylesheet -- did I miss it somewhere?
I think I'll write an entire Styling section in the README and talk about Teacup, NUI, Pixate, and of course the built-in ProMotion system. I'm not sure how far I'll take the built-in system (it's fairly good already for in-place styling) -- the above is an idea and really depends on whether devs would like something like that or if they already have a favorite styling system.
I think a mixture of systems would work fine too -- we don't have to only support one. Teacup 2.0 (https://github.com/colinta/teacup/tree/2.0) looks like it will be pretty good and include support for Pixate and NUI. So it probably makes sense to just incrementally improve the internal system and recommend some of the other systems for more complex needs.
The stylesheet i'm using is the NUI default, therefore no need to declare it :)
One neat thing about NUI is that you can call setAutoUpdatePath
with an absolute path for testing and as you save the stylesheet file, the interface in the simulator updates automatically! https://github.com/Skookum/RubyMotionTalk/blob/master/RedditRss/app/app_delegate.rb#L3
My only beef with NUI is that it's iOS 6 only... but when iOS 7 comes, out, I'll be dropping support for iOS 5.
iOS 5.1.1 is currently about 3% of my user base (roughly 10k people). Everyone else is on some versions of 6.
Yah, the NUI autoupdate is pretty cool, forgot abou that. :)
I have to admit tho, I do like Jamon's direction. Not that I couldn't do it in NUI or teacup, but this
style :logout_button do {
include: [ :login_button, :base_styles ],
background_color: UIColor.redColor,
font: fontWithName('Signika-Bold', size:20)
} end
just looks very nice and clean to me. And that's the reason I gravitated towards ProMotion - I like the abstraction and the way things are represented. I dunno, the syntax just speaks to me more than the others.
@jamon, you're probably right about the instantiation - would be interesting to see what the memory usage might be for curiosity's sake.
I think if it doesn't take too much to implement (which I don't think it will) we might do this. Just gives users another option. But I'll make it clear that ProMotion can be used with your favorite styling system.
It'll be in a later release. Perhaps 1.0.
Damn. Promotion is going to eclipse bubblewrap in terms of functionality soon :wink:
@markrickert Touché ...
I'm certainly willing to be talked out of this. I don't want to fall into the "not built here" syndrome. I agree with Jason that this syntax would make a lot of sense to me, though.
it seems as though you guys have accidentally added my account to this thread (jamon@github.com).
On Wed, May 8, 2013 at 11:29 AM, Jamon Holmgren notifications@github.comwrote:
@markrickert https://github.com/markrickert Touché ...
I'm certainly willing to be talked out of this. I don't want to fall into the "not built here" syndrome. I agree with Jason that this syntax would make a lot of sense to me, though.
— Reply to this email directly or view it on GitHubhttps://github.com/clearsightstudio/ProMotion/issues/46#issuecomment-17617388 .
With a name as cool as that, @jamon, you should be involved. :) Sorry about that. Feel free to unwatch, of course.
Oh, whoops, sorry. I should have paid more attention.
@jamonholmgren I guess it's up to you, and sure, we don't want to fall into the not built here thing, but like I was saying earlier, the approach your taking feels best to me. So I guess that's my two cents.
I really like this, to throw my 2c in. Also a fan of the on_touch event.. :)
I'm currently working on a way to integrate the parts of Teacup that can help us here.
Looking at Teacup in more detail, I think it will work fine as-is. Since Screens are UIViewControllers, you can use Teacup normally.
I would recommend you use the instance method syntax (rather than the class method syntax):
class StyleScreen < PM::Screen
title "Styles"
stylesheet :standard
def will_appear
@view_setup ||= begin
layout(self.view, :root) do
# these subviews will be added to `self.view`
subview(UIToolbar, :toolbar)
subview(UIButton, :hi_button)
end
true
end
end
end
I'll write up a styling with Teacup guide at some point and include it in the repo.
So does this mean PM will only use Teacup for styling or will it have its own styling as well?
@Jasonbit: ProMotion will continue to have its own lightweight styling system with set_attributes
and add
. I'll continue to develop that as well and may even incorporate some of the ideas we had here. But after looking into what they've done with Teacup I think it's a fine choice for more demanding styling requirements.
ProMotion now supports Teacup styling in a very simple way. Just add stylesheet
to your screen and stylename:
to your add
or set_attributes
calls.
class HomeScreen < PM::Screen
stylesheet :home
def will_appear
add UILabel.new, stylename: :teacup_style
end
end
I decided to write a guide to styling views. It uses the simplest solution; one that doesn't eat memory nor require any modification to the current ProMotion styling code.
https://github.com/clearsightstudio/ProMotion/wiki/Guide%3A-Styling-Your-Views
Essentially, you define a module that gets mixed into your screen. To extend, it's relatively easy using parentstyle.merge({ new_styles: :whatever})
. Just ruby, no magic. Then, you use that method in your add element call.
module MyStyles
def button_style
{
background_color: UIColor.whiteColor
}
end
end
class MyScreen < PM::Screen
include MyStyles
def on_load
add some_button, button_style
add some_other_button, button_style.merge({
some_override: :value
})
end
end
Has the add UILabel.new, stylename: :foo
for Teacup option disappeared? :)
It doesn't work for me, and I can't locate it in the source files anywhere.
Addressed your question in the PR.
I think the element styling needs more work and potentially a new paradigm.
add
method (previouslyadd_element
) works pretty well but still litters your controllers with view codeI'd like some ideas. Potential discussion points:
Of course, we should avoid forcing this styling paradigm on people and still work well with Teacup, Pixate, and other styling DSLs. But I think a built-in or included styling system would be a good idea.
Comments needed!