Skip to content
colinta edited this page Oct 24, 2014 · 2 revisions

Examine your views, examine your layers, examine your controllers, your sprite kit worlds, your application menus - and any tree structure you create yourself! SugarCube::Repl is a powerful debugging and development ally.

To really be handy you'll want to require the sugarcube-repl package, which makes the SugarCube::Repl methods available in the REPL (it does this by adding methods to Kernel that call the corresponding method in SugarCube::Repl).

Finding the view you want.

This is often touted as the single most useful feature of SugarCube!

(main)> tree
  0: . UIWindow(#6e1f950: [[0.0, 0.0], [320.0, 480.0]])
  1: `-- UIView(#8b203b0: [[0.0, 20.0], [320.0, 460.0]])
  2:     +-- UIButton(#d028de0: [[10.0, 10.0], [320.0, 463.400512695312]])
  3:     |   `-- UIImageView(#d02aaa0: [[0.0, 0.0], [320.0, 463.400512695312]])
  4:     +-- UIRoundedRectButton(#d02adb0: [[55.0, 110.0], [210.0, 20.0]])
  5:     |   `-- UIButtonLabel(#d02af00: [[73.0, 0.0], [63.0, 19.0]], text: "Button 1")
  6:     +-- UIRoundedRectButton(#d028550: [[60.0, 30.0], [200.0, 20.0]])
  7:     |   `-- UIButtonLabel(#d02afb0: [[68.0, 0.0], [63.0, 19.0]], text: "Button 2")
  8:     `-- UIRoundedRectButton(#d02b220: [[70.0, 30.0], [300.0, 20.0]])
  9:         `-- UIButtonLabel(#d02b300: [[118.0, 0.0], [63.0, 19.0]], text: "Button 3")

The SugarCube to_s package provides lots of to_s methods on UIKit objects - that makes this tree view really useful to find the view you want. Once you do find the one you want, you can fetch it out of that list using the adjust method, which is aliased to a to make it easy on the fingers.

(main)> a 6
=> UIRoundedRectButton(#d028550: [[60.0, 30.0], [200.0, 20.0]]), child of UIView(#8b203b0)

Now that we've chosen the button, it is available in the a method, and there are a bunch of methods in the SugarCube::Repl module functions that act on that object. Most of these methods help you adjust the frame of a view.

> up 1
> down 1  # same as `up -1`
> down  # defaults to 1
> left 10
> right 10
> left  # => left 1
> origin 10, 12  # move to x:10, y:12
> wider 15
> thinner 10
> taller  # => taller 1
> shorter  # => shorter 1
> size 100, 10  # set size to width:100, height: 10
> shadow(opacity: 0.5, offset: [0, 0], color: :black, radius: 1)  # also supports path, which is a CGPath object.
> center  # See `Centering` section below
> restore  # original frame and shadow is saved when you first call `adjust`

Here are the short versions of those methods.

> u          # up, default value=1
> d          # down
> l          # left
> r          # right
> w          # wider
> n          # thiNNer
> t          # taller
> s          # shorter
> o 10, 12   # origin
> o [10, 12]
> o CGPoint.new(10, 12)
> z 100, 10  # siZe, also accepts an array or CGSize
# and frame
> f [[0,0], [0,0]]
# sHadow
> h opacity: 0.5, offset: [0, 0], color: :black, radius: 1

# frame, size, origin, and shadow can also be used as getters
> f
[[0, 0], [320, 568]]
> o          # origin
[0, 0]
> z          # size
[320, 568]
> h          # this returns an object identical to what you can pass to `shadow`
{opacity: 0.5, offset: [0, 0], color: :black, radius: 1}

# and of course the `a` method returns the current object
> a
=> UITextField(#9ce6470, [[46, 214], [280, 33]], text: "hi!"), child of UIView(#10a6da20)

CENTER (in parent frame)

It is called as center(which_index, of_total_number, direction). The order can be changed, and all the arguments are optional. Default values are center(1, 1, 'h') (center the item horizontally).

You can set 'direction' using a string or symbol: 'horiz', 'vert', 'x', even 'x and y'. The method searches for the letters [xyhv].

Here are a few examples:

(main)> center
[[145.0, 30.0], [30.0, 200.0]]
UIRoundedRectButton.origin = [145.0, 30.0]
=> "[[145.0, 30.0], [30.0, 200.0]]"

In order to place that same button in the center of the screen - horizontally and vertically - you can use this shorthand syntax:

center :xy

If you have three buttons and want them spaced evenly (vertically) across their parent frame, you can accomplish that this way:

(main)> tree
  0: . UIWindow(#6e1f950: [[0.0, 0.0], [320.0, 480.0]])
  1: `-- UIView(#8b203b0: [[0.0, 20.0], [320.0, 460.0]])
  2:     +-- UIButton(#d028de0: [[10.0, 10.0], [320.0, 464]])
  3:     |   `-- UIImageView(#d02aaa0: [[0.0, 0.0], [320.0, 464]])
  4:     +-- UIRoundedRectButton(#d02adb0: [[55.0, 110.0], [210.0, 20.0]], text: "Button 1")
  5:     |   `-- UIButtonLabel(#d02af00: [[73.0, 0.0], [63.0, 19.0]])
  6:     +-- UIRoundedRectButton(#d028550: [[60.0, 30.0], [200.0, 20.0]], text: "Button 2")
  7:     |   `-- UIButtonLabel(#d02afb0: [[68.0, 0.0], [63.0, 19.0]])
  8:     `-- UIRoundedRectButton(#d02b220: [[70.0, 30.0], [300.0, 20.0]], text: "Button 3")
  9:         `-- UIButtonLabel(#d02b300: [[118.0, 0.0], [63.0, 19.0]])
=> UIWindow(#6e1f950, [[0.0, 0.0], [320.0, 480.0]])
# grab the first button, and center it vertically.  It is the first of three buttons
(main)> a 4; center 1, 3, :vert; center
[[55.0, 110.0], [210.0, 20.0]]
UIRoundedRectButton.origin = [55.0, 110.0]
=> "[[55.0, 110.0], [210.0, 20.0]]"
# grab the second button.  The first parameter changes to `2`, because this
# button is in the second position.
(main)> a 6; center 2, 3, :vert; center
[[60.0, 220.0], [200.0, 20.0]]
UIRoundedRectButton.origin = [60.0, 220.0]
=> "[[60.0, 220.0], [200.0, 20.0]]"
# grab the third button and place it in the third position
(main)> a 8; center 3, 3, :vert; center
[[10.0, 330.0], [300.0, 20.0]]
UIRoundedRectButton.origin = [10.0, 330.0]
=> "[[10.0, 330.0], [300.0, 20.0]]"

The calculated positions (x,y) are in the REPL output

Finding the [controller,layer,...] you want.

You can analyze other hierarchies, too! See below to add selectors for your own data structures.

  • UIView, obviously
  • UIViewController will show presented modal controllers, too.
  • CALayer is similar to the UIView output
  • SKNode for your SpriteKit games
  • NSMenu, NSMenuItem on OS X
  • NSWindow, NSWindowController, NSViewController these will all output the view hierarchy (OS X doesn't have a controller hierarchy, unfortunately)

There's even a handy root method to grab the rootViewController:

(main)> tree root
  0: . #<MainScreenController:0xac23b80>
  1: +-- #<ScheduleViewController:0x13185d00>
  2: |   +-- #<ScheduleTableController:0x131862f0>
  3: |   `-- #<ScheduleCalendarController:0x131bba90>
  4: +-- #<CameraViewController:0x13191380>
  5: +-- #<UINavigationController:0xac01ea0>
  6: |   `-- #<UITableViewController:0xac04e30>
  7: +-- #<PicturesViewController:0x1403ede0>
  8: `-- #<MessagesViewController:0x131a1bc0>
=> #<MainScreenController:0xac23b80>

If you have a tree structure and you want to output it using tree, you can do so by passing either a method name (that should return an array) or a block. The block will be passed the current 'root' object, and should return its children.

class Foo
  attr_accessor :children
end
(main)> foo = Foo.new
(main)> foo.children = [Foo.new,Foo.new,Foo.new]
(main)> tree foo, :children
(main)> tree foo, :children
  0: . #<Foo:0x12d6e0d0>
  1: +-- #<Foo:0x114146c0>
  2: +-- #<Foo:0x114149d0>
  3: `-- #<Foo:0x114149e0>

=> #<Foo:0x12d6e0d0 @children=[#<Foo:0x114146c0>, #<Foo:0x114149d0>, #<Foo:0x114149e0>]>
(main)> tree(foo) { |f| f.children }
  0: . #<Foo:0x12d6e0d0>
  1: +-- #<Foo:0x114146c0>
  2: +-- #<Foo:0x114149d0>
  3: `-- #<Foo:0x114149e0>

=> #<Foo:0x12d6e0d0 @children=[#<Foo:0x114146c0>, #<Foo:0x114149d0>, #<Foo:0x114149e0>]>

Custom tree structures

If you find yourself doing this a lot, consider registering your selector in the app delegate:

(main)> SugarCube::Repl.register_tree_selector(Foo, :children)
# or  > SugarCube::Repl.register_tree_selector(Foo) { |f| f.children }

(main)> tree(foo)
  0: . #<Foo:0x12d6e0d0>
  1: +-- #<Foo:0x114146c0>
  2: +-- #<Foo:0x114149d0>
  3: `-- #<Foo:0x114149e0>

=> #<Foo:0x12d6e0d0 @children=[#<Foo:0x114146c0>, #<Foo:0x114149d0>, #<Foo:0x114149e0>]>
Default tree item

If you are debugging a SpriteKit game, you might want to have tree output your node hierarchy, since tree(window) will be pretty boring. Easy:

(main)> SugarCube::Repl.set_default(@spritekit_scene)

Better yet, place this in the view or controller where you create your scene(s), and tree will always output your game info.