Look! UIAlertView is dating UITableView!
As you may have guessed, this is about UIAlertView containing a UITableView. Since I started playing around with GameKit, I had the issue that I couldn’t use the PeerPicker with Client/Server stuff..
I can’t really get into the stuff we are doing and where we are using it – but I can offer you some trick and code to have a UIAlertView displayed with a fully controllable UITableView.
I started off with making it a decorator.. After 20′ I had to give up, because it was just too complicated to decorate objects where you don’t really know what’s going on. So I had to subclass it – unfortunately, but anyway..
Let’s take a look at what you would probably like to have..
Client
UIAlertTableView *alert = [[UIAlertTableView alloc] initWithTitle:@"Select Option"
message:@"select option or create one"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Create", nil];
alert.tableDelegate = self;
alert.dataSource = self;
alert.tableHeight = 120;
And this should be the result:
Alright, so, we just add a UITableView to the UIAlertView as a Subview, right? Hold on, Tiger :)
First of all, if we set the message to nil, we want to have this:
And if we rotate, we want to have the nice effects! Like this:
If you only want to grab the code without BlahBlah: UIAlertTableView on Bitbucket.org (btw. I switched to bitbucket.org – but that’s another story)
So, let’s look at the code a bit:
UIAlertTableView.h
#import <UIKit/UIKit.h>
@class UIAlertView;
@interface UIAlertTableView : UIAlertView {
// The Alert View to decorate
UIAlertView *alertView;
// The Table View to display
UITableView *tableView;
// Height of the table
int tableHeight;
// Space the Table requires (incl. padding)
int tableExtHeight;
id dataSource;
id tableDelegate;
}
@property (nonatomic, assign) id dataSource;
@property (nonatomic, assign) id tableDelegate;
@property (nonatomic, readonly) UITableView *tableView;
@property (nonatomic, assign) int tableHeight;
- (void)prepare;
@end
You see: we subclass UIAlertView, we have a UITableView, we have a delegate which needs to implement the protocol as well as a dataSource. Straight forward, I’d say…
Now, as for the implementation, one would think: just overload the “show” method and insert the TableView as a subview etc. Well, that works – NOT. First of all: you have to resize your AlertView, then you have to move the buttons down and then you have to place the tableView somewhere (esoteric?).
OkOk, just overwrite the drawRect then? You are getting closer!
But, first, let’s have a look at the prepare method.
UIAlertTableView.m:prepare
- (void)prepare {
if (tableHeight == 0) {
tableHeight = 150.0f;
}
// Calculate the TableViewHeight with padding
tableExtHeight = tableHeight + 2 * kTablePadding;
tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
tableView.delegate = tableDelegate;
tableView.dataSource = dataSource;
// Insert it as the first subview
[self insertSubview:tableView atIndex:0];
}
This code creates the TableView but does not set a real frame yet. It sets the given DataSource and Delegate. To be totally correct, there should be a custom setTableDelegate / setDataSource method which changes them in the tableView – but this is left as an exercise to the reader :)
After the creation, we insert the tableView as the very first subview of the alertView – so we know where to find it again and that nothing is hidden because of the tableView.
Now comes the tricky part: drawing.
For that, we use a private API call to the AlertView, called layoutAnimated:(BOOL)animated.
We overload it in our custom subclass because the initial drawing and the drawing on setNeedsLayout goes through that method.
After that method is called, all the elements that belong to the AlertView (title, message, buttons …) are arranged, so we can use those values for our next computations.
So, this is how it goes:
UIAlertTableView.m:layoutAnimated
- (void)layoutAnimated:(BOOL)fp8 {
[super layoutAnimated:fp8];
[self setFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y - tableExtHeight/2, self.frame.size.width, self.frame.size.height + tableExtHeight)];
// We get the lowest non-control view (i.e. Labels) so we can place the table view just below
UIView *lowestView = [self.subviews objectAtIndex:0];
int i = 0;
while (![[self.subviews objectAtIndex:i] isKindOfClass:[UIControl class]]) {
UIView *v = [self.subviews objectAtIndex:i];
if (lowestView.frame.origin.y + lowestView.frame.size.height < v.frame.origin.y + v.frame.size.height) {
lowestView = v;
}
i++;
}
// TODO: calculate this value
CGFloat tableWidth = 262.0f;
tableView.frame = CGRectMake(11.0f, lowestView.frame.origin.y + lowestView.frame.size.height + 2 * kTablePadding, tableWidth, tableHeight);
for (UIView *sv in self.subviews) {
// Move all Controls down
if ([sv isKindOfClass:[UIControl class]]) {
sv.frame = CGRectMake(sv.frame.origin.x, sv.frame.origin.y + tableExtHeight, sv.frame.size.width, sv.frame.size.height);
}
}
}
Step by step: first, we call the superclass - so everything gets arranged.
Then, we set a new frame for the AlertView, which is the same frame + tableHeight + padding. But we also need to rearrange the frame - which is half of that additional height.
After that, we compute the lowest "sitting" normal view - no control object - in the alert view. We loop through it, until we find the first control element - which are usually the buttons, because they are added at the end. You could loop through all views and get the lowest from any non-UIControl objects. But this works :)
This position is used to place the TableView at the correct position - whether you have a message, a title or whatever there.. but it needs to be above the buttons - UI standards.
To be above the buttons, we need to rearrange the buttons, and this is what happens: all UIControl object in the AlertView are moved down by the size we added to the AlertView frame - the complete TableHeight. And this is it.
We resize the AlertView, calculate the position where the TableView is supposed to be inserted at and then move the buttons. If we rotate, this gets calculated again and nicely rearranged.
Note: you can apply the very same method for any other UIView object you want to insert into an UIAlertView.. Be it TextField or ImageView or whatever. (For textField, you should use their private APIs though)
Again, code is here: Code on Bitbucket.org
Use pastie.org with LaunchBar
Since a while, I’m having senile insomnia, so I thought, I should use it for good. One thing that always bugs me at work is the work with pastie (or any other paste-thing) because of the copy – paste – copy thing.
So I wrote a very little script to use it with LaunchBar.
What does it do? It takes your clipboard, pastes it to pastie and pastes the returned URL back into the clipboard..
Here is the script:
pastie.scpt
set pastieURL to "http://pastie.org/pastes"
try
set responseURL to (do shell script "curl http://pastie.caboo.se/pastes/create -H 'Expect:' -F 'paste[parser]=plaintext' -F 'paste[body]=" & (the clipboard) & "' -F 'paste[authorization]=burger' -s -L -o /dev/null -w '%{url_effective}'")
set the clipboard to responseURL
end try
Save this to “~/Library/Application Support/LaunchBar/Actions/” (works with LaunchBar 5) and it will detect it automatically. Type the name of your script and you’re done. So much for my insomnia :)
UPDATE: Just found out about Spark which let’s you define shortcuts for Apple Scripts – which is another nice way.
There are friends and there are friends..
If you’ve ever used “GottaGo“, you might have experienced it from time to time: The stations around you disappear on the “Closest” Screen.
Why does this happen? Let me explain.
So far, I used the nearby search from Google to get a list of stations around you. Fairly simple. But.. SBB.ch does i.e. not understand “Bäckeranlage” but “Zürich, Bäckeranlage” which makes sense. But Google only provides “Bäckeranlage” – not so nice to map here. I’ve seen a comment in the AppStore from someone who noted this problem.. And he is right. This gave me a hard time.. [The fact that I'm in the exam session was no helpful either but nevermind :)]
How was the problem solved so far? With reverse geocoding. With the (great) API provided by geonames.org, I could more or less flatten this problems out. More or less? Yes. In the logs (Yes, sorry, I keep logfiles, but it’s to improve the query results, nothing more. I don’t see any IDs or sth. Just from where to where you are going and what method was successful for your query) I saw that a lot of requests for simple station names had to be handled by the “Google Fallback” – which is obviously not intended. This “Google Fallback” should catch up, when/if you search for an address or a POI (like Subway Restaurant Zurich). Why is that? Because the city mapping from Swiss Postal Service and SBB are quite different. I.e. there is a “Klein-Basel” in their database, where there is only a “Basel” on the SBB side. Then there are various ambiguous names, like “Bern / Liebefeld” which is also no hit on SBB.ch .. I guess you see the point.
Summary: Google returns list a stations which do not have the same name on SBB.ch which cannot alway be corrected because the reverse geocoder is not always “right”.
What do you do in your darkest hours? Yes, you ask your friends. There are friends. I’d call them “GottaGo”’s Baywatch ;) I was looking for a solution. Since we plan to release the next iteration very soon and we need a lot of work to do. (Btw.: we is me and Stefan Sicher from sichr.com who is working on the new design like a crazy cow ;) – and no, that design from the last blogpost is not from him, that’s just a mock-design I used to illustrate the layout)
Again – solution. I asked the guys over at local.ch again to help me out. Guess what – they did. They didn’t even say “Yes, we will look into it” – no. Just “Yes, we do it. Just say what you need so I can do it _before_ holidays”. Really – how awesome is this? This will require a few more Horsepower to be squeezed out of my dear “gonzo” but it’s a real breath of fresh air – the light at the end of the tunnel ;)
So you might wonder, what they will provide me.. A lot. First of all: Station-names as on SBB.ch (really exactly the same) together with coordinates (WGS coordinates, they used CH1903 so far). Together with their superfast services and customized XML interface, this will free us from bloated information we don’t really need and exact information we really need.
Then there is autocomplete. I was really worried about that.. (Really really worried. I set this up on another server so I could do it with a different IP-Address in case SBB.ch would block this). I got full permission and a customized API to do autocomplete stuff on local.ch .. Again: superfast! (I know, SBB.ch does a lot more computing for that stuff, but it was really slow :))
As I read these lines, I wanted to tell Joel to bake a big cake so I could give it to the local team. For all those who asked me to open a Paypal account for donations or sth: abandon search.ch and use local.ch from now on! ;) that would result in a win – win situation for all of us :)
Again: I just want to thank you guys over at local.ch, namely Vasile, Patrice, Ebi and Joel for your effort. I hope the users will appreciate it as much as I do :)
Whatever will be, will be..
Well.. Since you folks are really into changes these days, I’ll provide you with a little sneak-preview of what’s coming in the v0.1.0 – Annapurna release.
First off: Language support. GottaGo will be available in english, german, french and italian. And as you see: A totally new UI. (The green disappeared! ;))
Then we have had one request to include Cabs.. Well, since I don’t want to waste my time on cars, I asked the guys over at local to help me a bit here. Thanks to the help of Tobias Ebnöther and Joel Bez of the local.ch team at Liip, this works like a charm.
And last but certainly not least, we have autocompletion. That’s the reason why the UI changed so much..
These are just the most significant changes. There are way more changes that you will notice. (Like track number.. But there will be _no_ track number for Zurich HB since this information is not available to me. (Try it on sbb.ch).
However, I cannot tell you about the exact release date. I’m having some exams the next two weeks, so I won’t have time to work a lot on this, but since I canceled release 0.0.2 (Dhaulagiri), this one might come around that time..
I want to thank you all out there for giving me many flowers and a few complaints. Makes working on this a little easier :)
The last few days, I’ve been browsing through the AppStore and I think it’s pretty cool, that 2 of the top 3 apps are not games in Switzerland. And as of the time of writing, neither is the number 1 app. In a lot of other AppStores, the games lead the rankings :) This might also change in the swiss AppStore in a few hours, but it’s cool that it takes a while..
Well.. here we go. In the AppStore
Whew.. Funny things happen, when you’re off for a day without internet connection. `GottaGo’ is out.
(In case you missed it: http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=285851523&mt=8 )
I got a few e-mails and private messages that – I think – contain questions that some of you may care about.
- Is this an official SBB App?
- Nope.
- If it’s not official, how long do you think this will stay?
- I think the few queries this App does and costs in computing power will be compensated by folks like you who use public transit. (Compensate as in help to improve the environment and pay your trips..)
- Why isn’t `this’ included as well?
- Because it is coming soon! :) No really: The interest of this app is to help. I got GREAT feedback from the beta testers (great as in helpful) which improved this App a lot! And I hope I get helpful feedback from a few more users.
- You did a great job.
- Thanks. But 70% of this goes to my testers because this app would be pretty.. uhm.. not useful.. without them :) Thanks a lot! (I’m still looking for testers btw.)
- Another big piece of this cake goes to Liip (which gave me time to work on this – you see, a great company to work for) and Stefan Sicher (who did the fine art)
- No this is not a big crying scene like “I wanna thank my mum, my cat, my toilet…” :)
- Will this stay free?
- Forever.
- Why is it all in English?
- I don’t know :) Actually, Apple needs at least english, but I haven’t had any interest in doing it in 2 languages. And I’m sorry to have missed this point. This will be improved in future releases.
Besides: I hope you enjoy the app. If not -> https://jira.liip.ch/browse/GGO (I’m especially interested in the guy who got directed through his town instead of walking to the station where this happened..)
A swiss public transit API (SBB and Google..)
Here we go.. After weeks of going back and forth between a crappy API (sbb.ch) with exact schedule and a nice API with wrong schedule (google.com), I finally merged them into one.
What are the capabilities of this API? Let’s make an example.
Given you entered from = “Feldstrasse 133, Zürich” and to = “Bern”.
- It then checks sbb.ch first – No hit.
- Then it checks google.com, which will return you a schedule with the next transit station from the address “feldstrasse+133, Zürich”
Ok, this is not a big deal.
In GottaGo, we often use (or almost everytime) the coordinates of a station. Let’s make another example.
Query: from=Triemli[8.495521,47.368052,0] and to=”Feldstrasse 133″.
The coordinates are then reverse geocoded, so that we get a city name.. Which is “Zürich” in this case.
- The API will first check sbb.ch for “Zürich, Triemli” to “Feldstrasse 133″, which will be no hit.
- It then checks sbb.ch for “Zürich, Triemli” to “Zürich, Feldstrasse 133″.
Note that the city is also added to the other parameter since we had no hit in the first place and the city name did not yet occur in the second parameter “Feldstrasse 133″. But no hit again..
- Then it checks google.com for “Zürich, Triemli” to “Feldstrasse 133″, which will be no hit, since there are a few more “Feldstrasse”s around in Switzerland.
- Ok, now the last resort: Check google with “Zürich, Triemli” to “Zürich, Feldstrasse 133″ – Ahhh, finally a hit :)
Why all this? Because of POI and address-book search.
SBB.ch doesn’t really provide a proper API for using POIs and addresses. And google doesn’t provide the comfort of saying “Fribourg” is the Mainstation of “Fribourg” and not the center of Fribourg, and the schedule is sometimes wrong, especially during holidays.
This results in a comfortable API which we can use to make also very simples queries like: from “Current Location” to “Liip” in `GottaGo’.
The URL to this API? http://couch.codesofa.com/api/transit.xml?from=station[longitude,latitude,0]; station2[longitude,latitude,0]&to=station3; station4[longitude,latitude,0]
(The coordinates are optional, and you can enter as many stations as you like, separated by “;”)
Eg: couch.codesofa.com/api/transit.xml?from=Triemli[8.495521,47.368052,0]&to=Bern
Btw.: Latest Beta of `GottaGo’ is out. (An iPhone app for swiss public transit). If you want a preview, see: http://liip.to/applyforbeta

