Pages

Showing posts with label iOS8. Show all posts
Showing posts with label iOS8. Show all posts

Friday, 5 February 2016

Developing a custom keyboard extension for iOS

I was recently working on a custom keyboard application. I was trying few things (which of course the client asked to do) for many hours, then later I came to know that they are not really possible. I thought I could share them here, so that it may be helpful to someone.

So before developing a custom keyboard extension for iOS, one should consider these things too.

1. No transparent background for the keyboard.
      You can't have a transparent background for your custom keyboard. By default the iOS provide a view which has the default keyboard's background. It may be in light/dark depending on the keyboard appearance (light/dark). You can, of course set any colour to your container view of the keyboard, but not the clearColor.

2. Your keyboard can't be a stand alone. You must have a Main app also. My focus was only on the keyboard, literally the Main app is useless in my case. So I added the About Us and Support screen where I provided some information about my client and instructions about how to enable/use my keyboard.

3. You can't share any information directly from your Main app with your keyboard and vice versa. You have to use App Groups for that. Since Keyboard extension is more like a separate app, both the keyboard and the main app could share the same app group. You can also store the data you don't want to share directly in the default.
   For e.g in my keyboard I stored the last used keyboard, last used theme... information like that directly in the user default. But I also had in app purchases and the user can purchase them only for the Main app, so I stored the purchases details with the app group defaults, which both the apps can share.

4. You can't draw key popups outside your keyboards bounds. So I inserted one more row (with some special characters which will be used more often) in the top of the keyboard, which don't have any popups. And the popups for the second row can be shown now without any problem

5.  Even you don't care about the decimal and special characters, all the keyboard apps must provide the decimal and special characters keyboard in order to pass the app review process.

6. Auto correction, text selection, cut/copy/paste - not possible.

7. Had some problems while deleting characters when using my custom keyboard in Safari address bar, which is normal

8. Use the current selected keyboard type and return key type from the textDocumentroxy and change your keyboard appearance and the return key title accordingly.

9. You have to provide the primary language for your custom keyboard in the info.plist file. Since my keyboard supports multiple language I can't use en/fr/it something like that, but there is a special option just for this. I used the key 'mul' in the primary language of the plist which means 'Multiple Languages'.


Tuesday, 17 February 2015

IBInspectable and IBDesignable

One of the cool feature that was introduced in the Xcode 6 is enabling interface builder attributes for your custom view. i.e., the attributes that appears in the right when you click a control in the interface builder. Your custom control can have its properties right in the interface builder itself.


The closest thing you would do previously is, add your custom properties in the User defined run time attributes.
Previously, when you use a custom control, you have to do all the things by code. But now with this new feature, you can design your custom view in the interface builder itself. You can see how your custom view will appear while designing. There will be a lot of open source libraries in the future using this.
Apple have introduced two new attributes to make this happen IB_DESIGNABLE and IBInspectable. You have to prefix IB_DESIGNABLE before your custom view and add IBInspectable as the attributes to its property. So, a sample class will look like this.


The following types can be used for IBInspectable.
  • Boolean (BOOL)
  • Number (NSInteger)
  • String (NSString)
  • Localized String
  • Point (CGPoint)
  • Size (CGSize)
  • Rect (CGRect)
  • Range (NSRange)
  • Color (UIColor)
  • Image (UIImage)
The properties that have the IBInspectable attribute can be configured in the attributes inspector of the interface builder. And they will appear in the User defined run time attributesalso. They both can be used interchangeably.


You can write your code in the .m file with the IBInspectable properties setters or the drawrect method.



You can also debug your custom view without running. Simply open a assistant editor and put a breakpoint in your code and Choose Editor > Debug Selected Views.
IBInspectable can be used in your custom UIView/UIControl subclasses, categories, extensions. Don’t do expensive coding with the IBInspectable properties. The interface builder will try to compile the code no longer than 200ms. If it can’t compile within this period, the Xcode gives error.

In the first screenshot, I have added a custom text field which is of course a subclass of the UITextfiled.I can change the border color of the text field from the interface builder itself.

The apple docs is here and the sample source code is in the github.




Grap the code and play around yourself and make some useful controls with this. 

Wednesday, 24 December 2014

Using constraints in storyboard and also by code


Sample code is in Github.


Three years before, all the iPhones sizes (iPhone3, iPhone3GS, iPhone4, iPhone4S) are the same. So we didn't care about the compatibility issues like the android developers. When apple introduced iPhone5 and later iPhone5S, iPhone5C, it was not a big deal for us. All we did was, add a launch image for those new screen sizes and we had to adjust our views a little to use the newly added 0.5 inches screen. So the auto layout helped us to achieve this.

Now apple introduces even big screen sizes, and the old style auto layout cannot help us like in the past. Because old auto layout only works with the super view i.e, if the super view expands it expands, when the superview move the control also will move. But you cannot specify how much you want to move or how much you want to expand. There is nothing more than this.

With using the auto layout, you can place a control in a view in its top left corner, but it is merely impossible to place a control at 30% of left. Also you cannot make a text field expands its width, half the width of its superview. But the Constraints does this trick.

When trying to learn this technology, it'll like a hell at first. But once you get into it, you will find the constraints as one of the best tools apple introduced ever. 

If you use the constraints in your app, you don't have to worry about these screen sizes. Even you don't have to worry about the iPad screen sizes. You can have a universal storyboard for all your devices.

Here I am trying start this, taking the following sample screen. The final output should look like this in Portait and Landscape.




1) The screen has a header in the top of the main view with 60 pixels height.
2) The header has two buttons in each corner with 10 pixels of padding and a header label in the middle.
3) A textfield is added to the view, which is 75 pixels below from the headerview and center to the main view. The width of this text field is half the width of the main view.
4) There are two buttons 25 pixels below the textfield. One button is left aligned with the textfield and the other one is right aligned with the textfield.

This is easy if you identify which constraint is to be added for each control in storyboard. But, it is little difficult to do in code. To meet all the above requirements, we have to add the following constraints.

You can find a sample project in Github illustrating this example both in storyboard and code. The link is here.


Create all the controls like you do normally, add those to the viewcontroller's view. Don't care about their positions. The constraints will do that.

Before adding the constraints to a subview, make sure you give No to its "translatesAutoresizingMaskIntoConstraints" property. Otherwise you will get an exception.


Constraints for the subviews of the header view.


Constraints for the header view.


Constraints for the textfield and the two button below it. (Make sure you disable the translatesAutoresizingMaskIntoConstraints for these controls before adding constraints.)


The alignment of the code here is worse, but believe me, it works.

- (void)viewDidLoad {
BOOL addControlProgrammatically = YES;
if (addControlProgrammatically) {
[self.view.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
[headerView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.5]];
[self.view addSubview:headerView];
UIButton *cancelBtn = [UIButton buttonWithType:UIButtonTypeSystem];
[cancelBtn setTitle:@"Cancel" forState:UIControlStateNormal];
[headerView addSubview:cancelBtn];
UIButton *saveBtn = [UIButton buttonWithType:UIButtonTypeSystem];
[saveBtn setTitle:@"Save" forState:UIControlStateNormal];
[headerView addSubview:saveBtn];
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 31)];
[textField setBorderStyle:UITextBorderStyleRoundedRect];
UIButton *button1 = [UIButton buttonWithType:UIButtonTypeSystem];
[button1 setTitle:@"Button1" forState:UIControlStateNormal];

UIButton *button2 = [UIButton buttonWithType:UIButtonTypeSystem];
[button2 setTitle:@"Button2" forState:UIControlStateNormal];
cancelBtn.translatesAutoresizingMaskIntoConstraints = NO;
saveBtn.translatesAutoresizingMaskIntoConstraints = NO;
[headerView addConstraints:@[[NSLayoutConstraint constraintWithItem:cancelBtn
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:headerView
attribute:NSLayoutAttributeLeading
multiplier:1.0 constant:10],
[NSLayoutConstraint constraintWithItem:cancelBtn
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:headerView
attribute:NSLayoutAttributeCenterY
multiplier:1.0 constant:0]
]];
[headerView addConstraints:@[[NSLayoutConstraint constraintWithItem:saveBtn
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:headerView
attribute:NSLayoutAttributeTrailing
multiplier:1.0 constant:-10],
[NSLayoutConstraint constraintWithItem:saveBtn
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:headerView
attribute:NSLayoutAttributeCenterY
multiplier:1.0 constant:0]
]];

headerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraints:@[[NSLayoutConstraint constraintWithItem:headerView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0 constant:60],
[NSLayoutConstraint constraintWithItem:headerView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0 constant:0],
[NSLayoutConstraint constraintWithItem:headerView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailing
multiplier:1.0 constant:0],
[NSLayoutConstraint constraintWithItem:headerView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0 constant:0]
]];
[self.view updateConstraintsIfNeeded];
textField.translatesAutoresizingMaskIntoConstraints = NO;
button1.translatesAutoresizingMaskIntoConstraints = NO;
button2.translatesAutoresizingMaskIntoConstraints = NO;
// [textField setText:@"Hello World"];
[textField setBackgroundColor:[UIColor redColor]];
[self.view addSubview:textField];
[self.view addSubview:button1];
[self.view addSubview:button2];
[self.view addConstraints:@[[NSLayoutConstraint constraintWithItem:textField
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0 constant:0],
[NSLayoutConstraint constraintWithItem:textField
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:headerView
attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:75],
[NSLayoutConstraint constraintWithItem:textField
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.5 constant:0]]];
[self.view addConstraints:@[[NSLayoutConstraint constraintWithItem:button1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:textField
attribute:NSLayoutAttributeLeft
multiplier:1.0 constant:0],
[NSLayoutConstraint constraintWithItem:button1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:textField
attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:25]]];
[self.view addConstraints:@[[NSLayoutConstraint constraintWithItem:button2
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:textField
attribute:NSLayoutAttributeRight
multiplier:1.0 constant:0],
[NSLayoutConstraint constraintWithItem:button2
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:textField
attribute:NSLayoutAttributeBottom
multiplier:1.0 constant:25]]];

}
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}