Another Programming Blog

(mostly iOS and Ruby on Rails)

iOS: Restrict Panning of UIPageViewController to a Certain Area

Another iOS code-snippet:

I had a UIPageViewController in scrolling-mode that would allow to pan between view-controllers using a finger gesture. However, I wanted to restrict the panning area to a certain area of the screen, for example as shown in this screenshot:

Restricted Panning Area UIPageViewController

I do it by subclassing the UIPageViewController, finding its UIScrollView, and adding a new UIPanGestureRecognizer to that scrollView.

I set my subclassed UIPageViewController to be the delegate of that new UIPanGestureRegognizer. I then implement two delegate methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import "LZPageViewController.h"

@interface LZPageViewController ()

@end

@implementation LZPageViewController
{
  UIPanGestureRecognizer *_scrollViewPanGestureRecognzier;
}

- (void)viewDidLoad
{
  [super viewDidLoad];
  // Do any additional setup after loading the view.

  for (UIView *view in self.view.subviews) {
    if ([view isKindOfClass:[UIScrollView class]])
    {
      UIScrollView *scrollView = (UIScrollView *)view;
      _scrollViewPanGestureRecognzier = [[UIPanGestureRecognizer alloc] init];
      _scrollViewPanGestureRecognzier.delegate = self;
      [scrollView addGestureRecognizer:_scrollViewPanGestureRecognzier];
    }
  }
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
  return NO;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
  if (gestureRecognizer == _scrollViewPanGestureRecognzier)
  {
    CGPoint locationInView = [gestureRecognizer locationInView:self.view];
    if (locationInView.y > SOME_VALUE)
    {
      return YES;
    }
    return NO;
  }
  return NO;
}

In the last override I decide if I want to “eat the event” (reply YES) or if I want the original UIPanGestureViewRecognizer of the UIScrollView to handle it (reply NO). So, the YES-reply means the UIPageViewController will not scroll to the next ViewController.

iOS: How to Resize and Rotate an UIImage - Thread Safe Version

Here are two code snippets for rotating and resizing UIImage’s without using UIGraphicsBeginImageContext(), because it is not thread-safe and can lead to weird behavior.

In my case the camera-preview when taking a picture in an app I was writing, turned black from time to time. The problem was that I was rotating and resizing the images using UIGraphicsBeginImageContext().

Here’s some code I’ve found and tweaked to resize images:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (UIImage *)imageByScalingToSize:(CGSize)targetSize
{
  UIImage *newImage = nil;

  CGFloat targetWidth = targetSize.width;
  CGFloat targetHeight = targetSize.height;

  CGContextRef bitmap = CGBitmapContextCreate(NULL,
                                              targetWidth,
                                              targetHeight,
                                              CGImageGetBitsPerComponent(self.CGImage),
                                              4 * targetWidth, CGImageGetColorSpace(self.CGImage),
                                              (CGBitmapInfo)kCGImageAlphaNoneSkipLast);

  CGContextDrawImage(bitmap, CGRectMake(0, 0, targetWidth, targetHeight), self.CGImage);

  CGImageRef ref = CGBitmapContextCreateImage(bitmap);
  newImage = [UIImage imageWithCGImage:ref];

  if(newImage == nil) NSLog(@"could not scale image");
  CGContextRelease(bitmap);

  return newImage ;
}

And here’s how to rotate them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (UIImage*)rotateInRadians:(CGFloat)radians
{
  CGImageRef cgImage = self.CGImage;
  const CGFloat originalWidth = CGImageGetWidth(cgImage);
  const CGFloat originalHeight = CGImageGetHeight(cgImage);

  const CGRect imgRect = (CGRect){.origin.x = 0.0f, .origin.y = 0.0f,
      .size.width = originalWidth, .size.height = originalHeight};
  const CGRect rotatedRect = CGRectApplyAffineTransform(imgRect, CGAffineTransformMakeRotation(radians));

  CGContextRef bmContext = NYXImageCreateARGBBitmapContext(rotatedRect.size.width, rotatedRect.size.height, 0);
  if (!bmContext)
      return nil;

  CGContextSetShouldAntialias(bmContext, true);
  CGContextSetAllowsAntialiasing(bmContext, true);
  CGContextSetInterpolationQuality(bmContext, kCGInterpolationHigh);

  CGContextTranslateCTM(bmContext, +(rotatedRect.size.width * 0.5f), +(rotatedRect.size.height * 0.5f));
  CGContextRotateCTM(bmContext, radians);

  CGContextDrawImage(bmContext, (CGRect){.origin.x = -originalWidth * 0.5f,  .origin.y = -originalHeight * 0.5f,
      .size.width = originalWidth, .size.height = originalHeight}, cgImage);

  CGImageRef rotatedImageRef = CGBitmapContextCreateImage(bmContext);
  UIImage* rotated = [UIImage imageWithCGImage:rotatedImageRef];

  CGImageRelease(rotatedImageRef);
  CGContextRelease(bmContext);

  return rotated;
}

Because the above takes radians, convert using something like this:

1
CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;};

Just put these methods into a category on UIImage.

iPhone/iOS Tutorial: How to Write an Image-cropper

This kept me busy for a while until I found a super simple way to do it: In this tutorial/code-snippet I’ll show you how to write an image-cropper for your iPhone-app.

What do I mean by image-cropper?

Bascially, it’s about giving your users a simple way to select part of an image using pan and pinch gestures. This can be handy when they upload a profile picture or something similar.

Here’s the end result:

How it’s done:

There is one UIScrollView with a large contentSize. Embedded in the scroll-view is a UIView (baseView). And embedded in the baseView is the UIImageView with the Ferrari-picture.

LZCropperExample sketch

The yellow boxes in the above image represent the iPhone screen and the cropping area.

Because the contentSize is bigger than the bounds of the screen (= frame of _scrollView), the user can pan to select the desired image. Because minimumZoomScale and maximumZoomScale are set, the user can pinch to zoom. (Although I am not sure why it helps to set the initial zoomScale after all other initialization. If it’s done earlier it won’t scroll until zoomed.)

The actual cropping is done by rendering the contents of the view-controller’s view into an ImageContext and then cutting out the portion that (here: roughly) corresponds to the non-dimmed “window” in the center.

The image is then saved to the phone’s photo gallery.

Get the example project from Github and take a look at the code. It’s actually very simple and self-explanatory.

Here’s the actual cropping code that is executed when you tap the “OK”-button.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- (void)pickResult:(id)sender // ehm.. not sure why I needed sender here.. ;)
{
  // some visual feedback a.k.a. special effects:

  _acceptButton.enabled = NO;

  _specialEffectsView.alpha = 1.0f;
  [UIView animateWithDuration:1.0f animations:^{
    _specialEffectsView.alpha = 0;
  } completion:^(BOOL finished) {
    _infoLabel.alpha = 1.0f;
    [UIView animateWithDuration:1.0f animations:^{
      _infoLabel.alpha = 0;
      _acceptButton.enabled = YES;
    }];
  }];

  // 1. hide stencil overlay

  _stencilView.hidden = YES;

  // 2. take a screenshot of the whole screen

  UIGraphicsBeginImageContext(self.view.frame.size);
  [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
  UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();

  // 3. pick interesting area and crop

  CGImageRef imageRef = CGImageCreateWithImageInRect([screenshot CGImage],
                                                     CGRectMake(35.0f, 159.0f, 248.0f, 248.0f));
  UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
  CGImageRelease(imageRef);

  // 4. just for fun, resize image by factor 2

  UIImage *resizedImage = [croppedImage resizedImageToSize:
                           CGSizeMake(croppedImage.size.width * 2, croppedImage.size.height * 2)];

  // 5. save result to photo gallery

  UIImageWriteToSavedPhotosAlbum(resizedImage, NULL, NULL, NULL);

  // 6. show stencil view again

  _stencilView.hidden = NO;
}

Very straightforward stuff.

But as I said, it took me a while. For some reason I never thought of taking a screenshot first.. ;)

Also this solution has the limitation of pixel size. If you want to crop an image of for example 10000 times 10000 pixels and get half of it, you would expect a 5000x5000 pixel result. However, because of the screenshot technique, the cropped area will never be bigger than the pixels on the iPhone screen.

Emailserver Page Is Offline

I’ve scrapped the website where I offer setting up your email-server. While it was fun to learn and experiment with, and while some have expressed interest in the offer, it’s neither pracitcal nor profitable for me to continue offering it.

Please refer to this post for information how to start with the email topic yourself. It describes a simple way of setting up an emailserver with Ubuntu.

I Ditched Google Analytics

I just ditched Google Analytics. It’s like a reflex to add it into website projects, but I don’t really need it. Nor do I want it. I think Google has enough information about us already. And they freely share with the American government it seems. So yeah, goodbye.

Now I’m using one of these tools. I can execute a shell script on my computer now and seconds later I have all the analytics I need. Good looking, useful and best of all, not shared with Google.

How to Setup a Courier E-mail Server With Ubuntu Linux

Introduction

There are very good reasons to want your own e-mail server. I’ve tried several setups and found the Courier-mailserver the simplest to work with. Here’s a tutorial on how to set it up.

This should take about 15 - 30 minutes.

Expected end-result

Here’s what you will have in the end:

  • Own email server where you can configure an unlimited number of email addresses with an unlimited number of domain-names. However, in this setup each address is bound to a user on the Linux system.
  • SMTP and IMAP with encryption. Even though email may travel unencrypted to it’s destination, the link between your mail client and your mail server should be encrypted.

Requirements

Here’s what you need:

  • An Ubuntu 13.10 server in the cloud (other Ubuntu version should work). You can get those for 5 euros a month or so. (Because a virtual server is just fine for this task. You don’t need 32 Gigs of RAM and 8 cores..)
  • A domain-name where you can configure DNS. Specifically: You want to change the MX-record.
  • Some basic knowledge of Linux.

Step-by-step gudie

Ok, I will explain to you now how to install the Courier mail suite. I know there are several popular other ones, but this just happens to be a guide about Courier.

If you get stuck or have special config needs, then please also refer to the end of the article where I list a few helpful links.

Installation

First, connect to your email-server using SSH. Make sure you can execute commands with sudo. Start with

1
sudo apt-get update

to update your packet sources.

Installation time:

1
sudo apt-get install courier-imap courier-imap-ssl courier-mta courier-authdaemon courier-mta-ssl courier-maildrop telnet

telnet is in there for testing. You can remove it if you you want, but telnet is nice to check if SMTP works and what its configuration is.

Configuration - Part 1

Enter your domain in /etc/courier/locals. Do the same in /etc/courier/esmtpacceptmailfor.dir/domains.

Run sudo makeacceptmailfor to have these changes accepted.

Create a user, let’s say “suntke” (I think that’s a Frisian first name..).

1
sudo useradd -d /home/suntke -m suntke

And create a password for him.

1
sudo passwd suntke

This user can login to your linux-box now using the password you specified. Now, let’s create a maildir for him.

1
2
3
4
cd /home/suntke
maildirmake Maildir
maildirmake -q 1000000000S Maildir
chown -R suntke:suntke Maildir

(prefix with sudo as needed. The -q flag sets a quota. Just use some high number..)

So, let’s say the domain-name was “example.com”, then this means you have just created the email-address “suntke@example.com”. Incoming email will be stored in /home/suntke/Maildir.

You can add as many users and domains as you like.

You can now go ahead and send and receive emails. However, I suggest you add some extra security through encryption.

Configuration - Part 2

In /etc/courier/esmtpd-ssl set AUTH_REQUIRED to 1. This forces authentication. In /etc/courier/esmtpd set ESMTPAUTH=”LOGIN PLAIN”.

/etc/courier/esmtpd.pem should exist and be not world-readable. This should automatically enable ESMTP STARTTLS, encrypted SMTP.

When you are done, you can restart everything: (added newlines for readabilty)

1
2
3
4
5
/etc/init.d/courier-authdaemon restart &&
/etc/init.d/courier-imap restart &&
/etc/init.d/courier-mta restart &&
/etc/init.d/courier-mta-ssl restart &&
/etc/init.d/courier-imap-ssl restart

Configuration - Part 3, Client-side, Incoming

So, this was the server-side. Let’s go ahead and configure a mail-client. I will jsut go ahead and use the fictional user/address suntke@example.com here, as well.

Here’s the settings in Mail on OSX. It’s pretty much analogous with Thunderbird etc.

For Accounts/Account Information:

1
2
3
4
5
6
7
8
9
10
11
12
Account Type: IMAP
Description: Example.com
Email Address: suntke@example.com
Full Name: Suntke Svensson ;)

Incoming Mail Server: example.com
User Name: suntke
Password: *******

Outgoing Mail Server (SMTP): example.com, configured below.

TLS Certificate: None

Advanced-Tab

1
2
3
IMAP Path Prefix: INBOX
Port: 993 with Use SSL checked.
Authentication: Password

Configuration - Part 4, Client-side, Outgoing

1
2
3
4
Description: example.com
Server Name: example.com

TLS Certificate: None

Advanced-Tab

1
2
3
4
5
6
7
Use default ports (25, 465, 587) checked.

Use Secure Sockets Layer (SSL) checked

Authentication: Password
User Name: test
Password: *******

Configuration - Part 5, DNS

Use your domain registrar’s console (or do it yourself) to set the MX record. In this case you’d just set example.com, because you didn’t configure any subdomains such as mail.example.com.

SSL Warnings

Your Mail-program or OS will probably complain about the certificates not being signed. You can decide if you want to have them signed. It doesn’t make much sense if it’s just for yourself. Mark the certificate as “trusted” or add them to your “trusted certs” collection in your keychain.

If it doesn’t work

Try restarting both the server processas as well as your mail-client. Make sure your system trusts the certificates. Double-check your passwords.

As I was trying to verify this tutorial on a testing server it wouldn’t work at first, but restarting the client and accepting the certs permanently did the trick.

What’s next?

That’s it, congratulations. You have your own email-webserver. Here’s a few ideas on what you could do next:

  • install spamassassin. It’s not difficult and I’m sure you can google it. Or I can write a short post about it in the future.
  • install GPGTools so you can encrypt your email end-to-end. (this is for Mac, similar software is available for every major OS though.)
  • install squirrel-webmail if you need/want a web-frontend. Not my kind of thing but I thought I’d mention it.
  • use a database to manage users.
  • write filters for courier to automate things.
  • install the whole thing on a tiny computer like the Raspberry Pi, use a dynamic-DNS vendor that support updating of MX records, configure your router to forward mail to your Pi, and host the whole thing at home. ;)

Links

Here are some links that might help you with Courier:

Comments, suggestions, bugs?

If you have something to add or correct, please let me know so I can update this guide. Let’s spare others extra work and frustration..

Update (April 16th, 2014)

In case you mess up along the way and then get errors like “Mailbox unavailable” (but you have already solved the underlying problem), you can reset a mailbox like this:

$ courier clear suntke@example.com 

I needed this recently when I could not figure out what the problem is. Turns out, I solved the problem earlier but didn’t clear the “broken mailbox flag”.

iOS: Configure a Different App-icon for Debug-builds

When developing an app, it’s sometimes nice to have a different logo for debug builds (play-icon in XCode) and archive builds. Especially when the versions behave differently.

Now, you can configure the different behavior by passing different values in “Preprocessor Macros” (Build Settings of your target in XCode), but here is just a quick tip on how to set different app-icons:

There’s many ways to go about it, including a prefix file, but I’m just setting the name of the icon file in “Preprocessor Definitions” like this:

plist Variables

And then in the plist itself I susbstitute the icon-filename with the variable name set above:

1
2
3
4
5
6
7
8
9
10
11
12
13
<... omitted ...>
<dict>
  <key>CFBundlePrimaryIcon</key>
  <dict>
      <key>CFBundleIconFiles</key>
      <array>
          <string>APP_ICON_FILE</string>
          <string>APP_ICON_FILE</string>
          <string>Default</string>
      </array>
  </dict>
</dict>
<... omitted ...>

And that’s about it.

Good Read: Informing Ourselves to Death

In this speech titled “Informing outselves to death”, Neil Postman discusses the rise of information technology and puts it into a broader historical context. He muses about wether it can help us to answer the old big questions of humanity.

I think this is a fantastic read, perhaps especially for folks from the industry like myself. (Because we tend to get too enthusiastic about the tools we create at times.) But it also serves as a warning for those in the inner/englightened circle of information professionals, as well as for those being left out.

The Buddy Cloud

Raspberry Pi

The Raspberry Pi board is a delightful device. I use it at home to play music. I just thought about what it would take to use it for file-storage, specifically, as a backup server.

The problem: One can plug in a harddisk and copy some files. But unless the harddisk is in a different place, it’s still vulnerable. If thieves come, they might steal your computer AND your backup-disk. If there is a fire, same problem.

Since the NSA has somewhat killed cloud-computing (or would you stil use Dropbox, etc.?), there’s a need for a new strategy.

Here’s the idea: The Buddy CloudTM.

In my protoype the following things are needed:

  • Raspberry Pi
  • USB-Disk
  • A buddy with the same equipment

Of course the equipment can vary, but the idea is simple: You keep his backups, he keeps yours. Encrypted, so he can’t read your data and you can’t read his.

(The device should always be plugged in so something low-power like the Pi seems useful.)

The syncing can be as simple as FTP, but it can get more sophisticated with rsync and more advanced algorithms.

What do you think? Buddies helping each other out by providing offsite-backups to each other.

Welcome to the post-cloud-computing world.

Average Salary

To calculate the average salary of a group of people anonymously:

Person #1 invents a (high enough) random number and adds his salary to it. He passes the sum to person #2. #2 adds his salary and passes the new sum to person #3. And so forth, until #1 eventually gets the last sum from the last participant.

Person #1 subtracts the initial random number and divides the result by the number of people. The result is the average salary.

Caveats: People can lie and the number of participants should be big enough. (With 2 people person #1 will just find out the other persons salary.)