Sunday, 29 September 2013

iOS 7 UINavigationController layout issue and workaround

Before iOS 7, UINavigationController layouts navigation bar above the view to be displayed. In iOS 7, the navigation bar is overlapping with the view to be displayed. This causes display issue for certain views. One workaround is through UINavigationControllerDelegate.

// Workaround for iOS7 UINavigationController layout issue.
// In iOS7, navigation bar is overlapping with the displayed view.
// Needs to shift it down by navigation bar height and shrink its height.
// But do not do this for table view.
- (void)navigationController:(UINavigationController *)navigationController
  didShowViewController:(UIViewController *)vc
animated:(BOOL)animated {
    if(!IOS7_OR_UP)
        return;
    if ([vc.view isKindOfClass:NSClassFromString(@"UITableView")])
        return;
    
    static int barH = 0;
    if (barH == 0) {
        UIView* bar = findViewOfClass(vc.view.superview.superview.superview, NSClassFromString(@"UINavigationBar"));
        barH = bar.frame.size.height;
    }
    
    CGRect frame = vc.view.frame;
    frame.size.height -= barH;
    frame.origin.y = barH;
    vc.view.frame = frame;

}

iOS 7 UITabBarController layout issue and workaround

Before iOS 7, UITabBarController layout the tab bar below the view to be displayed. However, in iOS 7, the tab bar is overlapping with the view to be displayed. This causes some display issues for some views drawn by user.

One workaround is to shrink the size of the view to be displayed by the UITabBarController by subclass UITabBarController.

@interface MyTabBarController : UITabBarController

@end

@implementation MyTabBarController

// Workaround for iOS UITabBarController layout issue.
// In iOS7, the tab bar overlaps with the view to be displayed.
// The height of the view to be displayed with a UITabBarController needs
// to be shrinked by the height of the tab bar.
-(void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    
    if (!IOS7_OR_UP)
        return;
    
    static int barHeight = 0;
    if (barHeight == 0) {
        UIView* bar = findViewOfClass(self.view, NSClassFromString(@"UITabBar"));
        barHeight = bar.frame.size.height;
    }
    UIView* CV = (UIView*)self.view.subviews.firstObject;
    CV.autoresizesSubviews = YES;
    UIView* GCV = (UIView*) CV.subviews.firstObject;
    UIView* GGCV = (UIView*) GCV.subviews.firstObject;
    CGRect frame = CV.frame;
    frame.size.height = self.view.frame.size.height - barHeight;
    CV.frame = frame;
    GCV.frame = frame;
    GGCV.frame = frame;
    
}

@end

Friday, 26 July 2013

OpenGL ES: How to rotate around a specific point instead of origin?

If you want to rotate around a point (x0, y0, z0), assuming

matrix-vector multiplication order is vector * matrix,

your current modelview matrix is M1,

the rotation matrix is MR,

translation matrix from (0, 0, 0) to (x0, y0, z0) is T(x0, y0, z0),

After rotation, the modelview matrix should be

M1*T(-x0, -y0, -z0) * MR * T(x0, y0, z0)

Note if (x0, y0, z0) is in world coordinates, it needs to be first transformed to eye coordinates by

(x0, y0, z0) * M1


OpenGL ES: Is modelview matrix column major or row major?

Modelview matrix is column major. In memory, it is like

M00, M10, M20, M30, M01, M11, M21, M31, M02, M12, M22, M32, M03, M13, M23, M33

A translation of (x, y, z) is

1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1

In GLSL, matrix vector multiplication is defined as matrix * vector.

In host program, depending on how you define your matrix vector multiplication operator, it can be either matrix * vector or vector * matrix.

For a series of transformations M1, M2, M3,

if you define matrix-vector multiplication as matrix * vector,  the total transformation is

M3 * M2 * M1

If you define it as vector * matrix, the total transformation is

M1 * M2 * M3

Monday, 24 June 2013

iOS: How to Debug UIView Layout Issues

Go to https://github.com/glock45/iOS-Hierarchy-Viewer

Download and add it to your project.

When the layout issue shows up in iOS simulator, open a web browser and go to the iOS Hierarchy Viewer page (shown in app debug output at program launch.)

Starting from UIWindow, check frame of the UIViews until the problematic UIView.

Friday, 14 June 2013

CMake Configuration Failure due to Python

CMake configuration may fail due to python version issue. Sometimes a project requires an old version of python but CMake uses the newer version of python, which causes the failure. On Windows with GUI version of CMake, this can be resolved by choose the advanced option and modify the used python path to be an old version of python.

Saturday, 8 June 2013

UIKit: Stop Touches in a Subview being Handled by its Superview

If view A has a gesture recognizer, and this view has a subview B which does not have gesture recognizer, the gesture happened in the subview B will be recognized by the gesture recognizer of view A.

This may not be what you want. For example, if subview B is showing an introduction, you do not want a tap in it to be handled by view A.

One solution is to make view A a delegate of its gesture recognizer, and define a method:

-(BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch {
    return touch.view == self;
}

In this way, the gesture recognizer will not recognize gestures happen in its subview.

Wednesday, 5 June 2013

How to move files to another directory in Xcode 4

Move files physically to the desired directory, then open the project in Xcode 4, select the group, in the right "Identity and Type" panel, click the strange looking icon beside "Path" item, and choose the new directory of your files.

P.S. If your files are under version control, you cannot simply move files with Finder or mv command. You need to move them through version control system, otherwise you are in big trouble.

Sunday, 2 June 2013

iOS: How to find your artist id in app store

Go to http://itunes.apple.com/linkmaker and search for your name, you will find your artist id and all your app ids.

UIKit: programmatically created subview or control has wrong location

If you programmatically create subview or control, you better do it in viewDidAppear. Only at that time your view's size is finalized. Before that, if you use your view's size to determine the location of your subview or control, it may be wrong since your view may be resized.

Another solution is to use setAutoresizingMask, e.g., if your button is at the bottom of the screen, you need setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin. If the view height changes, your button goes with it.

iOS: share methods between classes

Two classes can shared methods through category. For example, you have two subclasses of UIViewController. You want to add some common methods to both, then you can create a UIViewController category which contains these common methods.

Saturday, 25 May 2013

iOS: macro for minimum supported target iOS version (deployment target)

Each Xcode project has a deployment target, that is the minimum iOS version your application should support.

Sometimes you need to use different code for different deployment target, here is an example:

#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
[self presentModalViewController:webViewController animated:YES];
#else
[self presentViewController:webViewController animated:YES completed:nil];

#endif

Thursday, 23 May 2013

UIKit: Simple way to disable gesture recognizer in subview

Sometimes you have gesture recognizer with a view but want to disable it for a subview (e.g. you use the subview as a toolbar). By default you can click "through" the subview as if it does not exist, which can cause confusion. A simple way to disable gesture recognizer in a subview is in UI builder to drag new a gesture recognizer onto the subview, which associates a gesture recognizer with the subview which does nothing when the gesture is recognized.

If you want the child of the subview (e.g. some buttons in this subview) to receive taps, uncheck its "Canceled in view" property.

OpenGL ES 1.1: How to convert world coordinates to UIView coordinates

    Assuming world coordinates is in worldp of type vec4, UIView window size is in bounds.size, the following code converts world coordinates to UIView coordinates:

    mat4 modelview;
    mat4 projection;
    glGetFloatv(GL_MODELVIEW_MATRIX, (float*)modelview);
    glGetFloatv(GL_PROJECTION_MATRIX, (float*)projection);
    vec4 eyep = worldp * modelview * projection;
    eyep = eyep/eyep.w;
    screenp.x = (1+eyep.x)/2*bounds.size.width;
    screenp.y = (1-eyep.y)/2*bounds.size.height;

Friday, 17 May 2013

Eclipse: recover overwritten files

If you overwrote a new file with an old file by mistake, Eclipse may be your only life saver. It has a feature called local history, which automatically records all the changes you made to a file. If you want to revert a file to an old edition, just right click the file in the left panel, and choose team/local history, then select a date to recover.

That's one reason I like Eclipse. Actually today it just saved me several days' of work when I accidentally reverted my file to an old revision in a version control system by mistake.

Sunday, 12 May 2013

OpenGL ES 1.1: texture mapped object has wrong color

If you use texture to get exact color for an object, make sure at the texture coordinate the texture does not change color suddenly. If so, you will get interpolated color instead of exact color. If the texture coordinates are at the corner or boundary of the texture, make sure the color at the other 3 corners or the reflected boundary are the same, otherwise you may get unwanted interpolated color.

OpenGL ES 1.1: lighted texture mapped object is too dark

Usually the texture is used as the diffuse material for the object. It is timed with the diffuse light to get the diffuse part of the lighted object color.

If the object looks too dark, you can increase the luminance of the diffuse light. If the luminance of the diffuse light is already 1, you may want to increase the luminance of your texture.


OpenGL ES 1.1 how to choose proper value for ambient/diffuse/specular color for light and material

Usually the color of a lighted object is summation of three parts: ambient, diffuse and specular.

The ambient part is the ambient light color times the object ambient material color. It does not depend on light position nor eye position.

The diffuse part is the diffuse light color times the object diffuse material color times a factor. It depends on light position but not eye position.

The specular part is the specular light color times the object specular material color times a factor. It depends on both light position and eye position.

There could be an emission part, which is similar to ambient part but does not need a light.

To view the contribution of the three parts to the final object color, you can enable just one type of light or material and disable other types of light or material and see the rendered object.

Since the three parts are summed together, you cannot let them all have strength 1, otherwise the color of the object becomes too bright like a photo with over exposure. You need to give each part a weight so that their summation does not exceed 1 in most positions.

The diffuse part provides most of the visual cue to the shape of the object. Therefore I usually give it a weight of 0.7, that is, diffuse light illuminance times diffuse material illuminance is around 0.7.

 You may want some ambient color if you do not want to have dark part of the object. However, too much ambient part gives your object a washed out feeling, so you do not want too much of it. A weight of 0.3 seems to be a good choice.

The specular part gives a shiny spot on your object. Usually a weight of 0.1 is good choice.  There is another parameter called shininess to control how much this shiny spot spreads out. A value of about 100 gives you a small bright spot whereas a value of 10 gives you less bright shiny patches.



Thursday, 9 May 2013

iOS: cannot find In-App-Purchase product in sandbox app store

You should be able to see In-App-Purchase products in sandbox app store even before you submit your application for review. Changes made in iTunes connect about In-App-Purchase should be immediately seen in sandbox app store.

One cause of problem is your IAP product ID is not unique. IAP product ID needs to be unique in the app store, not just unique in your app. A product ID like "Upgrade" won't work. You need to make it really unique by using reverse domain name like "com.mycompany.mypapp.myprod".

Another requirement is that your app bundle ID needs to match what you use in iTunes Connect. You need to first create a new bundle ID supporting IAP in iOS developer's portal then use it to create a new app in iTunes Connect.

If you change your bundle ID in Xcode, you need to delete the old app in iPhone simulator manually, otherwise Xcode may get confused and run the old app, which will confuse you since all your changes seem to have no effect.

Monday, 6 May 2013

iOS: Using lazy loading to improve application loading time

If your application needs to load some large resource and do some lengthy processing before it can display something, you need to do those in a thread other than the main UI thread. Otherwise your user will see your default launch image for a long time and may become impatient.

This is called lazy loading. However it introduces complexity into your application since now some resources may be not ready when you display your content.

If you display content by polling changes (e.g. OpenGL ES doFrame), your resource processing threads need to let the display thread know content has changed and the display thread needs to check if a resource is ready and then decide how to display.

Otherwise, the resource processing threads need to update the view by sending message or notification to the view controller.

Sunday, 5 May 2013

iOS: OpenGL ES frame is not updated for changes made in another thread

It is common to use display link in iOS to set a frame rate for OpenGL ES and use a function doFrame to update frame regularly.

-(void) doFrame {

  if (_needUpdate) {      // line A
    if ( // condition is satisfied) {
      // draw the scene
      _needUpdate = false;  // line B
    }
  }

}

In another thread, you do the following

//change the scene
_needUpdate = true;  // line C

so that the scene will be redrawn in doFrame.

However there is a bug with this approach. If execution time between line A and line B is long, there is high chance that line C happens between line A and line B and has no effect. The symptom is that frame is not updated for changes made in another thread.

One simple workaround is to move line B up and make the execution time between line A and line B short. Use a counter instead of boolean for _needUpdate could be another solution.





Saturday, 4 May 2013

Workaround about sorting English ordinal numbers or Roman numbers

You may want to sort texts containing English ordinal numbers or Roman numbers by their numerical order instead of dictionary order. For example

1st word
2nd word
3rd word
4th word

Richard I
Richard II
Richard III
Richard IV

One simple workaround for this is to encode them as simple numbers in the text, e.g.

#T001 word
#T002 word
#T003 word
#T004 word

Richard #R01
Richard #R02
Richard #R03
Richard #R04

then they can be sorted by dictionary order. When you need to display them, convert them to ordinal number or Roman number.

Binary file read by std::ifstream is corrupted

By default, std::ifstream is constructed with text mode. On windows, it will convert new line character which may corrupt the binary file.

The fix is to use std::ifstream::binary as the second parameter when construct ifstream for binary file.

Thursday, 25 April 2013

Python: re.sub does not replace all matches

Trying to replace patterns in a multiline string:

re.sub(pattern, replacement, string, re.M)

This does not replace all matches, because the fourth parameter is the number of matches to be replaced. The fix is

re.sub(pattern, replacement, string, flags=re.M)

Saturday, 13 April 2013

iOS: OpenGL ES: implementing color picking

Color picking is a method to pick an object by rendering different objects with different color and reading pixel at the picking point.

To implement color picking for OpenGL ES on iOS, you need to make two changes:

1. in your touch handler, record the location of the touch and convert it to OpenGL window coordinates.

2. in your renderer, add a special rendering pass before your regular rendering pass. In this special rendering pass, clean the scene,  disable lighting and rendering each object with different color, then read the pixel at the saved picking position and convert it to object id. After that, clean the scene and render again with your normal rendering pass. Since the result of the special rendering pass is overwritten by the normal render pass, the user never sees it.

iOS: OpenGL ES disable/enable lighting changes light position

If you disable lighting then re-enable it, the light position will depend on the modelview matrix when you enable the lighting. This can causes light position to change between frames. This may be a bug of iOS 6.1.

If you want to keep the light position fixed when re-enable lighting, you need to set light position again after re-enabling lighting. Also you need to make sure the modelview matrix is the same each time you set the light position.

iOS: Adding UIGestureRecognizer causes subview not responding to touches

If you have an UIView which contains some subview such as buttons, adding UIGestureRecognizer will cause these subviews not responding to touches. The reason is by default the gesture recognizer handles all touches and discard them, therefore the subviews do not receive any touch.

The fix is adding


    recognizer.cancelsTouchesInView = NO;

http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIGestureRecognizer_Class/Reference/Reference.html

Friday, 12 April 2013

iOS: OpenGL window coordinates calculated from location in UIView is wrong

Usually a point's OpenGL window coordinates can be calculated from its location in UIView by:

    CGPoint pt = [gestureRecognizer locationInView:self];
    pt.y = self.bounds.size.height - pt.y;

However this calculation could be wrong if OpenGL viewport size does not match UIView size. 

I tried to resize OpenGL viewport at various point to match UIView size but did not succeed. Finally I got a workaround which allows me to get the correct OpenGL window coordinates:

    float viewport[4];
    glGetFloatv(GL_VIEWPORT, viewport);
    pt.y = (1.0f - pt.y/self.bounds.size.height) * viewport[3];