motion-kit / motion-kit

The RubyMotion layout and styling gem. Follow @MotionKit on twitter for updates and commit notifications
MIT License
317 stars 30 forks source link

Creating a standalone view #111

Open fappelman opened 9 years ago

fappelman commented 9 years ago

I have been using MotionKit and I have run into a problem which I cannot solve. I am trying to develop and OSX application.

The issue here is that I would like to create a new class that inherits from NSView using MotionKit as the layout engine.

I have worked through the various samples and I have created the first outline of the UI which works great. So far so good.

In the application I use an NSCollectionView which has to be provided a prototype. In this case the prototype is a class named MailViewPrototype.

collection_view.setItemPrototype(MailViewPrototype.new)
collection_view.maxNumberOfColumns = 1
collection_view.setContent(["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz”])

The class itself is straight forward:

class MailViewPrototype < NSCollectionViewItem
 def loadView
   self.setView(MailView.alloc.initWithFrame([[0,0],[400,340]]))
 end
 def setRepresentedObject(object)
   super(object)
   self.view.setViewObject(object)
 end
end

and the simplified version of the MailView class is this:

class MailView < NSView

 attr_accessor :message

 FONT_SIZE = 13

 def simple_box(frame)
   box = NSBox.alloc.initWithFrame(frame)
   # box.setTitlePosition NSNoTitle
   # box.setBorderType NSNoBorder
   # box.setBorderType NSGrooveBorder
   box
 end

 MIN_HEIGHT = 50

 def initWithFrame(frame)
   width = frame.last.first
   font = NSFont.fontWithName("Arial", size: FONT_SIZE)
   font_height = height_for_string("abc",font,width)
   puts "Font height = #{font_height}"
   # Add half of the font above and below the line
   frame = [[0, 0], [400, 80]]
   # Some Adhoc logic for now
   frame_height = font_height * 1.5 > MIN_HEIGHT ? font_height * 1.5 : MIN_HEIGHT
   frame = [[0, 0], [400, frame_height]]
   super(frame)
   # super([0,0],[width,height])
   box = simple_box(frame)
   self.addSubview(box)

   @message = NSTextField.alloc.initWithFrame([[0,0],[width,font_height]])
   @message.drawsBackground = false
   @message.setEditable false
   @message.setSelectable false
   @message.stringValue = "dummy value"
   @message.setBezeled false
   @message.setFont font
   box.addSubview(@message)

   self
 end

 def setViewObject(object)
   return if object.nil?
   puts "Setting message to #{object}"
   @message.stringValue = object
   @object = object
 end
end

I would like to use MotionKit for this class as well. I created the classed MailViewLayout and MailViewController to control this.

class MailViewLayout < MK::WindowLayout
 FONT_SIZE = 13

 def layout
   add NSBox, :left_box do
     frame [[0,0], ['100%','100%']]
     add NSTextField, :message do
       draws_background false
       editable false
       selectable false
       string_value "dummy value"
       bezeled false
       font NSFont.fontWithName("Arial", size: FONT_SIZE)
     end
   end
 end
end

class MailViewController < NSWindowController

 def init
   super.tap do
     @layout = MailViewLayout.new
     self.window = @layout.window
   end
 end

 def get(selector)
   @layout.get(selector)
 end

end

From here I run into a wall. The difference with the demo apps is here that this NSView needs to be created and I have no clue how to create a MailView class that would use my new MailViewController. My knowledge about Cocoa is limited so there is a good chance that my question is not MotionKit specific but any hint would be appreciated.

colinta commented 9 years ago

This is kind of tricky, and it touches on an edge case of MotionKit that I’m not pleased with: Cells (in iOS, in OSX there are also Prototypes like you are using) do not have a corresponding “Controller” where the Layout can live.

You can use a pattern that people have used to work with UITableViewCells. Assign the layout to the view, and assign the view as the root layout object.

 def loadView
    view = MailView.alloc.initWithFrame([[0,0],[400,340]])
    view.layout = MailViewLayout.new(root: view)  # add and style subviews here
   self.setView(view)
 end

I’ve considered a change to the MotionKit::Layout class so that it is actually a subview of NSView (UIView on iOS). We would also provide subclasses of common classes, like MotionKit::TableViewCell. This would break compatibility, though, so don't expect this soon..

If we implement the Layout < NSView refactor, this would be easier:

class MailView < MK::View
  def layout  # as usual
end
fappelman commented 9 years ago

I will try what you proposed. Appreciate the answer and you have my vote to refactor this to a view.