rubymotionr 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +1 -0
- data/lib/rubymotionr.rb +7 -0
- data/lib/rubymotionr/project.rb +18 -0
- data/lib/rubymotionr/repo.rb +18 -0
- data/lib/rubymotionr/version.rb +3 -0
- data/rubymotionr.gemspec +19 -0
- data/spec/.DS_Store +0 -0
- data/spec/multiple-rubymotion-apps/Beers/README +5 -0
- data/spec/multiple-rubymotion-apps/Beers/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Beers/app/app_delegate.rb +16 -0
- data/spec/multiple-rubymotion-apps/Beers/app/beer.rb +22 -0
- data/spec/multiple-rubymotion-apps/Beers/app/beer_details_controller.rb +15 -0
- data/spec/multiple-rubymotion-apps/Beers/app/beer_list_controller.rb +40 -0
- data/spec/multiple-rubymotion-apps/Beers/app/beer_map_controller.rb +51 -0
- data/spec/multiple-rubymotion-apps/Beers/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Beers/resources/list.png +0 -0
- data/spec/multiple-rubymotion-apps/Beers/resources/map.png +0 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/.gitignore +5 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/README +5 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/Rakefile +9 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/app/app_delegate.rb +8 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/app/cell.rb +13 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/app/circle_layout.rb +41 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/app/view_controller.rb +33 -0
- data/spec/multiple-rubymotion-apps/Fonts/README +7 -0
- data/spec/multiple-rubymotion-apps/Fonts/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Fonts/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Fonts/app/detail_controller.rb +50 -0
- data/spec/multiple-rubymotion-apps/Fonts/app/root_controller.rb +65 -0
- data/spec/multiple-rubymotion-apps/Fonts/resources/.gitkeep +0 -0
- data/spec/multiple-rubymotion-apps/GestureTable/README +8 -0
- data/spec/multiple-rubymotion-apps/GestureTable/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/UIColor_extensions.rb +32 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/UITableView_extensions.rb +7 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/gesture_recognizer.rb +361 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/transformable_cells.rb +92 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/view_controller.rb +173 -0
- data/spec/multiple-rubymotion-apps/GestureTable/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Hello/README +3 -0
- data/spec/multiple-rubymotion-apps/Hello/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Hello/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Hello/app/hello_view.rb +33 -0
- data/spec/multiple-rubymotion-apps/Hello/app/hello_view_controller.rb +5 -0
- data/spec/multiple-rubymotion-apps/Hello/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Hello/resources/icon.png +0 -0
- data/spec/multiple-rubymotion-apps/HelloGL/README +9 -0
- data/spec/multiple-rubymotion-apps/HelloGL/Rakefile +11 -0
- data/spec/multiple-rubymotion-apps/HelloGL/app/app_delegate.rb +16 -0
- data/spec/multiple-rubymotion-apps/HelloGL/app/index_data.rb +19 -0
- data/spec/multiple-rubymotion-apps/HelloGL/app/motiongl_controller.rb +139 -0
- data/spec/multiple-rubymotion-apps/HelloGL/app/vertex_data.rb +22 -0
- data/spec/multiple-rubymotion-apps/HelloGL/resources/rubymotion.png +0 -0
- data/spec/multiple-rubymotion-apps/Locations/README +3 -0
- data/spec/multiple-rubymotion-apps/Locations/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Locations/app/app_delegate.rb +11 -0
- data/spec/multiple-rubymotion-apps/Locations/app/location.rb +21 -0
- data/spec/multiple-rubymotion-apps/Locations/app/locations_controller.rb +66 -0
- data/spec/multiple-rubymotion-apps/Locations/app/locations_store.rb +61 -0
- data/spec/multiple-rubymotion-apps/Locations/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/README +5 -0
- data/spec/multiple-rubymotion-apps/Mustache/Rakefile +10 -0
- data/spec/multiple-rubymotion-apps/Mustache/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Mustache/app/mustache_view_controller.rb +99 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/guido.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/jmccolor.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/kay.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/matz.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/mustache.png +0 -0
- data/spec/multiple-rubymotion-apps/Paint/README +3 -0
- data/spec/multiple-rubymotion-apps/Paint/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Paint/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Paint/app/paint_view.rb +57 -0
- data/spec/multiple-rubymotion-apps/Paint/app/paint_view_controller.rb +13 -0
- data/spec/multiple-rubymotion-apps/Paint/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Paint/resources/erase.caf +0 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/README +8 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/Rakefile +10 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/app/app_delegate.rb +10 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/app/web_view_controller.rb +48 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/resources/index.html +211 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/resources/index2.html +14 -0
- data/spec/multiple-rubymotion-apps/README.rdoc +47 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/README.md +42 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/Rakefile +7 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/app/app_delegate.rb +12 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/app/custom_view_controller1.rb +31 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/app/custom_view_controller2.rb +29 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/app/helpers.rb +4 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/resources/Master.storyboard +127 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/.gitignore +5 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/Rakefile +7 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/app/app_delegate.rb +8 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/app/board.rb +59 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/app/board_view_controller.rb +86 -0
- data/spec/multiple-rubymotion-apps/Timer/README +3 -0
- data/spec/multiple-rubymotion-apps/Timer/Rakefile +7 -0
- data/spec/multiple-rubymotion-apps/Timer/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Timer/app/timer_controller.rb +38 -0
- data/spec/multiple-rubymotion-apps/Timer/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/README +6 -0
- data/spec/multiple-rubymotion-apps/Trollify/Rakefile +9 -0
- data/spec/multiple-rubymotion-apps/Trollify/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Trollify/app/image_composition_controller.rb +193 -0
- data/spec/multiple-rubymotion-apps/Trollify/app/uiimage_extension.rb +27 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/ballmer.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/icon.png +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/icon@2x.png +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/trollface.png +0 -0
- data/spec/multiple-rubymotion-apps/Tweets/README +3 -0
- data/spec/multiple-rubymotion-apps/Tweets/Rakefile +7 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/data_parser.rb +10 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/json_parser.rb +12 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/tweet.rb +11 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/tweet_cell.rb +51 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/tweets_controller.rb +72 -0
- data/spec/multiple-rubymotion-apps/Tweets/resources/.gitignore +0 -0
- data/spec/repo_spec.rb +27 -0
- data/spec/rubymotion-example/.DS_Store +0 -0
- data/spec/rubymotion-example/README +3 -0
- data/spec/rubymotion-example/Rakefile +7 -0
- data/spec/rubymotion-example/app/app_delegate.rb +9 -0
- data/spec/rubymotion-example/app/data_parser.rb +10 -0
- data/spec/rubymotion-example/app/json_parser.rb +12 -0
- data/spec/rubymotion-example/app/tweet.rb +11 -0
- data/spec/rubymotion-example/app/tweet_cell.rb +51 -0
- data/spec/rubymotion-example/app/tweets_controller.rb +72 -0
- data/spec/rubymotion-example/resources/.gitignore +0 -0
- data/spec/spec_helper.rb +9 -0
- metadata +309 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class UIImage
|
|
2
|
+
def imageRotatedByRadians(radians)
|
|
3
|
+
# Calculate the size of the rotated view's containing box for our drawing space.
|
|
4
|
+
rotatedViewBox = UIView.alloc.initWithFrame(CGRectMake(0,0,self.size.width, self.size.height))
|
|
5
|
+
t = CGAffineTransformMakeRotation(radians)
|
|
6
|
+
rotatedViewBox.transform = t
|
|
7
|
+
rotatedSize = rotatedViewBox.frame.size
|
|
8
|
+
|
|
9
|
+
# Create the bitmap context.
|
|
10
|
+
UIGraphicsBeginImageContext(rotatedSize)
|
|
11
|
+
bitmap = UIGraphicsGetCurrentContext()
|
|
12
|
+
|
|
13
|
+
# Move the origin to the middle of the image so we will rotate and scale around the center.
|
|
14
|
+
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2)
|
|
15
|
+
|
|
16
|
+
# Rotate the image context.
|
|
17
|
+
CGContextRotateCTM(bitmap, radians)
|
|
18
|
+
|
|
19
|
+
# Now, draw the rotated/scaled image into the context.
|
|
20
|
+
CGContextScaleCTM(bitmap, 1.0, -1.0)
|
|
21
|
+
CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), self.CGImage)
|
|
22
|
+
|
|
23
|
+
newImage = UIGraphicsGetImageFromCurrentImageContext()
|
|
24
|
+
UIGraphicsEndImageContext()
|
|
25
|
+
newImage
|
|
26
|
+
end
|
|
27
|
+
end
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class AppDelegate
|
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
|
|
4
|
+
@window.rootViewController = TweetsController.alloc.initWithStyle(UITableViewStylePlain)
|
|
5
|
+
@window.rootViewController.wantsFullScreenLayout = true
|
|
6
|
+
@window.makeKeyAndVisible
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class JSONParser
|
|
2
|
+
def self.parse_from_url(url)
|
|
3
|
+
data = DataParser.parse(url)
|
|
4
|
+
|
|
5
|
+
error_ptr = Pointer.new(:object)
|
|
6
|
+
json = NSJSONSerialization.JSONObjectWithData(data, options:0, error:error_ptr)
|
|
7
|
+
unless json
|
|
8
|
+
raise error_ptr[0]
|
|
9
|
+
end
|
|
10
|
+
json
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Tweet
|
|
2
|
+
attr_reader :author, :message, :profile_image_url
|
|
3
|
+
attr_accessor :profile_image
|
|
4
|
+
|
|
5
|
+
def initialize(dict)
|
|
6
|
+
@author = dict['from_user_name']
|
|
7
|
+
@message = dict['text']
|
|
8
|
+
@profile_image_url = dict['profile_image_url']
|
|
9
|
+
@profile_image = nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
class TweetCell < UITableViewCell
|
|
2
|
+
CellID = 'CellIdentifier'
|
|
3
|
+
MessageFontSize = 14
|
|
4
|
+
|
|
5
|
+
def self.cellForTweet(tweet, inTableView:tableView)
|
|
6
|
+
cell = tableView.dequeueReusableCellWithIdentifier(TweetCell::CellID) || TweetCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:CellID)
|
|
7
|
+
cell.fillWithTweet(tweet, inTableView:tableView)
|
|
8
|
+
cell
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initWithStyle(style, reuseIdentifier:cellid)
|
|
12
|
+
if super
|
|
13
|
+
self.textLabel.numberOfLines = 0
|
|
14
|
+
self.textLabel.font = UIFont.systemFontOfSize(MessageFontSize)
|
|
15
|
+
end
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fillWithTweet(tweet, inTableView:tableView)
|
|
20
|
+
self.textLabel.text = tweet.message
|
|
21
|
+
|
|
22
|
+
unless tweet.profile_image
|
|
23
|
+
self.imageView.image = nil
|
|
24
|
+
Dispatch::Queue.concurrent.async do
|
|
25
|
+
profile_image_data = NSData.alloc.initWithContentsOfURL(NSURL.URLWithString(tweet.profile_image_url))
|
|
26
|
+
if profile_image_data
|
|
27
|
+
tweet.profile_image = UIImage.alloc.initWithData(profile_image_data)
|
|
28
|
+
Dispatch::Queue.main.sync do
|
|
29
|
+
self.imageView.image = tweet.profile_image
|
|
30
|
+
tableView.delegate.reloadRowForTweet(tweet)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
self.imageView.image = tweet.profile_image
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.heightForTweet(tweet, width)
|
|
40
|
+
constrain = CGSize.new(width - 57, 1000)
|
|
41
|
+
size = tweet.message.sizeWithFont(UIFont.systemFontOfSize(MessageFontSize), constrainedToSize:constrain)
|
|
42
|
+
[57, size.height + 8].max
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def layoutSubviews
|
|
46
|
+
super
|
|
47
|
+
self.imageView.frame = CGRectMake(2, 2, 49, 49)
|
|
48
|
+
label_size = self.frame.size
|
|
49
|
+
self.textLabel.frame = CGRectMake(57, 0, label_size.width - 59, label_size.height)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class TweetsController < UITableViewController
|
|
2
|
+
def viewDidLoad
|
|
3
|
+
@tweets = []
|
|
4
|
+
searchBar = UISearchBar.alloc.initWithFrame(CGRectMake(0, 0, self.tableView.frame.size.width, 0))
|
|
5
|
+
searchBar.delegate = self;
|
|
6
|
+
searchBar.showsCancelButton = true;
|
|
7
|
+
searchBar.sizeToFit
|
|
8
|
+
view.tableHeaderView = searchBar
|
|
9
|
+
view.dataSource = view.delegate = self
|
|
10
|
+
|
|
11
|
+
searchBar.text = 'xcode crash'
|
|
12
|
+
searchBarSearchButtonClicked(searchBar)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def searchBarSearchButtonClicked(searchBar)
|
|
16
|
+
query = searchBar.text.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
|
|
17
|
+
url = "http://search.twitter.com/search.json?q=#{query}"
|
|
18
|
+
|
|
19
|
+
@tweets.clear
|
|
20
|
+
Dispatch::Queue.concurrent.async do
|
|
21
|
+
json = nil
|
|
22
|
+
begin
|
|
23
|
+
json = JSONParser.parse_from_url(url)
|
|
24
|
+
rescue RuntimeError => e
|
|
25
|
+
presentError e.message
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
new_tweets = []
|
|
29
|
+
json['results'].each do |dict|
|
|
30
|
+
new_tweets << Tweet.new(dict)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Dispatch::Queue.main.sync { load_tweets(new_tweets) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
searchBar.resignFirstResponder
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def searchBarCancelButtonClicked(searchBar)
|
|
40
|
+
searchBar.resignFirstResponder
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load_tweets(tweets)
|
|
44
|
+
@tweets = tweets
|
|
45
|
+
view.reloadData
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def presentError(error)
|
|
49
|
+
# TODO
|
|
50
|
+
$stderr.puts error.description
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tableView(tableView, numberOfRowsInSection:section)
|
|
54
|
+
@tweets.size
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def tableView(tableView, heightForRowAtIndexPath:indexPath)
|
|
58
|
+
TweetCell.heightForTweet(@tweets[indexPath.row], tableView.frame.size.width)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def tableView(tableView, cellForRowAtIndexPath:indexPath)
|
|
62
|
+
tweet = @tweets[indexPath.row]
|
|
63
|
+
TweetCell.cellForTweet(tweet, inTableView:tableView)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def reloadRowForTweet(tweet)
|
|
67
|
+
row = @tweets.index(tweet)
|
|
68
|
+
if row
|
|
69
|
+
view.reloadRowsAtIndexPaths([NSIndexPath.indexPathForRow(row, inSection:0)], withRowAnimation:false)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
File without changes
|
data/spec/repo_spec.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rubymotionr::Repo do
|
|
4
|
+
describe "point it at a rubymotion project with no dependencies and one project at the root" do
|
|
5
|
+
let(:repo) {
|
|
6
|
+
Rubymotionr::Repo.new(File.join(File.dirname(__FILE__), "rubymotion-example"))
|
|
7
|
+
}
|
|
8
|
+
it 'should parse one project' do
|
|
9
|
+
repo.projects.length.should == 1
|
|
10
|
+
end
|
|
11
|
+
it 'should find the path to be in the spec folder' do
|
|
12
|
+
repo.projects.first.path.should == File.join(File.dirname(__FILE__), "rubymotion-example/")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "point it at a rubymotion project with no dependencies and many projects at the root" do
|
|
17
|
+
let(:repo) {
|
|
18
|
+
Rubymotionr::Repo.new(File.join(File.dirname(__FILE__), "multiple-rubymotion-apps/"))
|
|
19
|
+
}
|
|
20
|
+
it 'should parse one project' do
|
|
21
|
+
repo.projects.length.should == 15
|
|
22
|
+
end
|
|
23
|
+
it 'should have found 15 paths all within multiple-rubymotion-apps directory' do
|
|
24
|
+
Dir.glob(File.join(File.dirname(__FILE__), "multiple-rubymotion-apps/*/")).sort.should == repo.projects.collect { |proj| proj.path }.sort
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class AppDelegate
|
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
|
|
4
|
+
@window.rootViewController = TweetsController.alloc.initWithStyle(UITableViewStylePlain)
|
|
5
|
+
@window.rootViewController.wantsFullScreenLayout = true
|
|
6
|
+
@window.makeKeyAndVisible
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class JSONParser
|
|
2
|
+
def self.parse_from_url(url)
|
|
3
|
+
data = DataParser.parse(url)
|
|
4
|
+
|
|
5
|
+
error_ptr = Pointer.new(:object)
|
|
6
|
+
json = NSJSONSerialization.JSONObjectWithData(data, options:0, error:error_ptr)
|
|
7
|
+
unless json
|
|
8
|
+
raise error_ptr[0]
|
|
9
|
+
end
|
|
10
|
+
json
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Tweet
|
|
2
|
+
attr_reader :author, :message, :profile_image_url
|
|
3
|
+
attr_accessor :profile_image
|
|
4
|
+
|
|
5
|
+
def initialize(dict)
|
|
6
|
+
@author = dict['from_user_name']
|
|
7
|
+
@message = dict['text']
|
|
8
|
+
@profile_image_url = dict['profile_image_url']
|
|
9
|
+
@profile_image = nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
class TweetCell < UITableViewCell
|
|
2
|
+
CellID = 'CellIdentifier'
|
|
3
|
+
MessageFontSize = 14
|
|
4
|
+
|
|
5
|
+
def self.cellForTweet(tweet, inTableView:tableView)
|
|
6
|
+
cell = tableView.dequeueReusableCellWithIdentifier(TweetCell::CellID) || TweetCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:CellID)
|
|
7
|
+
cell.fillWithTweet(tweet, inTableView:tableView)
|
|
8
|
+
cell
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initWithStyle(style, reuseIdentifier:cellid)
|
|
12
|
+
if super
|
|
13
|
+
self.textLabel.numberOfLines = 0
|
|
14
|
+
self.textLabel.font = UIFont.systemFontOfSize(MessageFontSize)
|
|
15
|
+
end
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fillWithTweet(tweet, inTableView:tableView)
|
|
20
|
+
self.textLabel.text = tweet.message
|
|
21
|
+
|
|
22
|
+
unless tweet.profile_image
|
|
23
|
+
self.imageView.image = nil
|
|
24
|
+
Dispatch::Queue.concurrent.async do
|
|
25
|
+
profile_image_data = NSData.alloc.initWithContentsOfURL(NSURL.URLWithString(tweet.profile_image_url))
|
|
26
|
+
if profile_image_data
|
|
27
|
+
tweet.profile_image = UIImage.alloc.initWithData(profile_image_data)
|
|
28
|
+
Dispatch::Queue.main.sync do
|
|
29
|
+
self.imageView.image = tweet.profile_image
|
|
30
|
+
tableView.delegate.reloadRowForTweet(tweet)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
self.imageView.image = tweet.profile_image
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.heightForTweet(tweet, width)
|
|
40
|
+
constrain = CGSize.new(width - 57, 1000)
|
|
41
|
+
size = tweet.message.sizeWithFont(UIFont.systemFontOfSize(MessageFontSize), constrainedToSize:constrain)
|
|
42
|
+
[57, size.height + 8].max
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def layoutSubviews
|
|
46
|
+
super
|
|
47
|
+
self.imageView.frame = CGRectMake(2, 2, 49, 49)
|
|
48
|
+
label_size = self.frame.size
|
|
49
|
+
self.textLabel.frame = CGRectMake(57, 0, label_size.width - 59, label_size.height)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class TweetsController < UITableViewController
|
|
2
|
+
def viewDidLoad
|
|
3
|
+
@tweets = []
|
|
4
|
+
searchBar = UISearchBar.alloc.initWithFrame(CGRectMake(0, 0, self.tableView.frame.size.width, 0))
|
|
5
|
+
searchBar.delegate = self;
|
|
6
|
+
searchBar.showsCancelButton = true;
|
|
7
|
+
searchBar.sizeToFit
|
|
8
|
+
view.tableHeaderView = searchBar
|
|
9
|
+
view.dataSource = view.delegate = self
|
|
10
|
+
|
|
11
|
+
searchBar.text = 'xcode crash'
|
|
12
|
+
searchBarSearchButtonClicked(searchBar)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def searchBarSearchButtonClicked(searchBar)
|
|
16
|
+
query = searchBar.text.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
|
|
17
|
+
url = "http://search.twitter.com/search.json?q=#{query}"
|
|
18
|
+
|
|
19
|
+
@tweets.clear
|
|
20
|
+
Dispatch::Queue.concurrent.async do
|
|
21
|
+
json = nil
|
|
22
|
+
begin
|
|
23
|
+
json = JSONParser.parse_from_url(url)
|
|
24
|
+
rescue RuntimeError => e
|
|
25
|
+
presentError e.message
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
new_tweets = []
|
|
29
|
+
json['results'].each do |dict|
|
|
30
|
+
new_tweets << Tweet.new(dict)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Dispatch::Queue.main.sync { load_tweets(new_tweets) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
searchBar.resignFirstResponder
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def searchBarCancelButtonClicked(searchBar)
|
|
40
|
+
searchBar.resignFirstResponder
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load_tweets(tweets)
|
|
44
|
+
@tweets = tweets
|
|
45
|
+
view.reloadData
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def presentError(error)
|
|
49
|
+
# TODO
|
|
50
|
+
$stderr.puts error.description
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tableView(tableView, numberOfRowsInSection:section)
|
|
54
|
+
@tweets.size
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def tableView(tableView, heightForRowAtIndexPath:indexPath)
|
|
58
|
+
TweetCell.heightForTweet(@tweets[indexPath.row], tableView.frame.size.width)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def tableView(tableView, cellForRowAtIndexPath:indexPath)
|
|
62
|
+
tweet = @tweets[indexPath.row]
|
|
63
|
+
TweetCell.cellForTweet(tweet, inTableView:tableView)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def reloadRowForTweet(tweet)
|
|
67
|
+
row = @tweets.index(tweet)
|
|
68
|
+
if row
|
|
69
|
+
view.reloadRowsAtIndexPaths([NSIndexPath.indexPathForRow(row, inSection:0)], withRowAnimation:false)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|