Showing posts with label uiview. Show all posts
Showing posts with label uiview. Show all posts

Sunday, July 11, 2010

Wall Paper Editor: editing text

The next stage now that I can move views around is to enable editing text.  I modified the text view editor nib so that it now includes a UITextView as a member.  I disabled editing and user interaction, and eliminated the scrollbars.  I sized it to the full size of the view.

Otherwise when the user attempted to drag the view, it would start editing instead.



 I have no scrolling, and the view itself has no interaction.  This way it is just a display for text.  I can programmatically control when it can be edited.

View Mover -- register taps


First the view mover was modified so that when a single tap is received it will call the proper routine in the delegate.
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"touches count %d, event touches for my view %d",[touches count],
  [[event touchesForView:parentView] count]);
// Handle single tap.
if ([delegate respondsToSelector:@selector(singleTap:)])
{
for (UITouch *touch in touches)
{
if (touch.tapCount==1)
{
NSLog(@"Sending singleTap");
[delegate singleTap:currentView];
}
}
}
etc..


Controller use taps to set subview


Next the controller was modified so that it would call new routines on the subview to enable input.  First it would tell any current view to stop editing, and then tell the a subview (if one was selected) to start editing.  Note that because single taps are registered anywhere on the UIImageView, this means that selecting something outside your current subview will cause they editing to stop.
- (void) singleTap:(UIView *)subViewTapped
{
NSLog(@"Received single tap");

// Go ahead and stop.
[self.currentPinchView stopEditing];
for (TextDisplayView *view in self.currentViews)
{
if (view.view==subViewTapped)
{
self.currentPinchView=view;
[view selectForEditing];
break;
}
}
}



Subview respond to taps and enable input



Two new routines exist to handle stopping and starting the taps.  Just so that I can visually see something happening, I also change the alpha and background color as the sequence occurs.
- (void) selectForEditing
{
self.view.alpha=0.4;
self.view.userInteractionEnabled=true;
self.textView.userInteractionEnabled=true;
[self.textView becomeFirstResponder];
}
- (void) stopEditing
{
self.view.alpha=0.3;
self.view.backgroundColor=[UIColor greenColor];
[self.textView resignFirstResponder];
self.view.userInteractionEnabled=false;
self.textView.userInteractionEnabled=false;
}



Known Bugs


This has one major problem.  A view near the bottom will be hidden by the keyboard.  I need to change how things are displayed so that this does not occur.

Friday, June 18, 2010

Watch your method names.

OK, I was cruising along nicely, when I started to get the following error:
exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the  nib but the view outlet was not set.

After some debugging I found that I had overridden setView function for another purpose.  This messed up the property causing the above error to occur.   


Saturday, June 12, 2010

View controller not loading view from .nib

I ran into a new debugging problem where a view controller was not showing anything from it's nib file.  It was just transparent.  I attempted to debug it, reconnected all the interface builder items.  I finally figured out that I had implemented loadView instead of viewDidLoad to do some initialization.  This caused the nib file to be ignored, the code was expecting me to programmatically create my view, which I wasn't doing.

There was no obvious error for this one, figuring it out cost a couple of hours.

Sunday, May 23, 2010

Math Drill: Changing aesthetics and better use of screen space

The previous buttons I had, were functional, however they certainly were not a good use of screen space.  In addition to have them work at all, I had to have a fair bit of gutter space all around my GUI just for the buttons at the left and right sides of the screen.  I did some rewriting so that the buttons will now display directly underneath the choice, and in addition I modified the math problem so that it returns results in numeric order.
The old GUI was this:

The new version displays a set of buttons centered under the current problem.  However if the current problem is at the left or right bounds of the display area, the list of choices will be justified with the display area.
The choice display when centered.  Note the ugly colors.  They were done so that I could check my math and see the bounds of the view.  I'm going to do a color sweep later on to ensure that they are better.







The left edge of the screenand the right edge.

This makes better use of the screen space, and also allows me a little more flexibility.  Note that each math problem currently calculates it's solution space one time, and always displays the same numbers.  That way you can't choose it multiple times and see what numbers remain.


The code



The new math problem result calculations


Not the most elegant code, but N is going to be at most 8, so it's probably more efficient than a more advanced sort to do it 'brute force'.

- (NSArray *)calculateChoices:(int) maxDisplayedValues
{
if (self.resultChoices!=nil)
return self.resultChoices;
NSMutableArray *set=[[NSMutableArray alloc]init];
for (int i=0;i<9;i++)
{
if (i==correctResult)
continue;
[set addObject:[NSNumber numberWithInt:i]];
}
NSNumber *correctResultO=[NSNumber numberWithInt:correctResult];

    NSMutableSet *returnValues=[[NSMutableSet alloc]init];
[returnValues addObject:correctResultO];
for (int i=0;i<(maxDisplayedValues-1);i++)
{
int randomalue=arc4random()%[set count];
NSNumber * value=(NSNumber *)[set objectAtIndex:randomalue];
[set removeObject:value];
[returnValues addObject:value];
if ([set count]==0)
break;
}
[set release];
NSLog(@"Calculated choices count %d,max %d",[returnValues count], maxDisplayedValues);
// N^2, but N is small.
NSMutableArray *res=[[NSMutableArray alloc]init];
while ([returnValues count]>0)
{
NSNumber *min=nil;
for (NSNumber* num in returnValues)
{
if ((min==nil)||([min intValue]>[num intValue]))
min=num;
}
[returnValues removeObject:min];
[res addObject:min];
}
[returnValues release];
self.resultChoices=res;
return res;
}




The new choice display code


Note that in the new display code I calculate the widths of the buttons to include the spacing between them.  I also did a similar modification to have some spacing for the entire problem set.


- (id) initWithChoices:(UIView *) targetView choices:(NSArray*)possibleChoices
{
int maxColumns=4;
int maxRows=2;
int buttonSpacing=5;
int height=targetView.frame.size.height/2;
// Allocate twice the size of the width of the view for buttons.

int selectionViewWidth=targetView.frame.size.width*2;
//There will be maxColumns-1 spaces in the view, so subtract that out before dividing to get the
// width
int targetWidth=(selectionViewWidth-(maxColumns-1)*buttonSpacing)/maxColumns;
// Center the view under the current problem. (in the coordinates of the view that contains the problem.
int originx=targetView.frame.origin.x-selectionViewWidth/2+targetView.frame.size.width/2;
// Put it below the current problem.
int originy=targetView.frame.origin.y+targetView.frame.size.height;
// Clamp it to the leftmost coordinates of the parent view.
if (originx<0)
originx=0;
// Clamp it to the rightmost coordinates of the parent view.
if ((originx+selectionViewWidth)> targetView.superview.frame.size.width)
originx=targetView.superview.frame.size.width-selectionViewWidth;
self.view =[[UIView alloc] initWithFrame:CGRectMake(originx, 
originy, selectionViewWidth, 
targetView.frame.size.height)];
// Ugly background color so I can see the limits of the view.
self.view.backgroundColor=[UIColor colorWithRed:255 
  green:0 blue:255 alpha:129];
int row=0;
int column=0;
for (NSNumber *value in possibleChoices)
{
UIButton *button=[[UIButton alloc]init] ;
button.tag=[value intValue]; 
button.frame=CGRectMake(column*(targetWidth+buttonSpacing),height*row,targetWidth,height-buttonSpacing);
button.backgroundColor=[UIColor redColor];
button.userInteractionEnabled=true;
[button setTitle:[value stringValue] forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonChosen:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
column++;
if (column>=maxColumns)
{
column=0;
row++;
}
if (row>=maxRows)
break;
}

return self;
}


The new calculations for the math problems


I modified this to allocate enough space at the bottom of the screen for the solutions, it would probably be more 'Apple like' to instead zoom to the problem for extra space, but I like this solution.   I also added a little spacing between the problems themselves.


- (void) displayProblems
{
int numRows=6;
int numColumns=5;
if ((self.interfaceOrientation==UIInterfaceOrientationLandscapeLeft)||
(self.interfaceOrientation==UIInterfaceOrientationLandscapeRight))
{
numRows=5;
numColumns=6;
}
int problemSpacing=10;
// make the bottom margin equal to the a max size of the problems/2.  Note that views outside the
// displayed area don't seem to respond to buton pushes, so we want to stay out of ther if possible.
int bottomMargin=(displayArea.frame.size.height/numRows);
int width=(displayArea.frame.size.width-problemSpacing*(numColumns-1))/numColumns;
int height=(displayArea.frame.size.height-bottomMargin-problemSpacing*(numRows-1))/numRows;
int column=0;
int row=0;
for (MathProblem *problem in currentProblems)
{
problem.view.frame=CGRectMake(column*(width+problemSpacing), (height+problemSpacing)*row, width, height);
column+=1;
if (column>=numColumns)
{
column=0;
row+=1;
}
}
[self setProblemNumber:self.currentProblem];

}

Buttons not responding.

In playing around with my new MathDrill GUI, I'm adding subviews that display data for choices.  I'm placing these in a view that has easy coordinates for my subview, however what I've found is that if the view I'm placing has buttons outside the space on the screen for the superview, that the buttons DO NOT RESPOND.   This is annoying.  

The convertRect and convertPoint routines are essential here.  Do your math in the most convenient space for you, then use some built in utility routines to convert it to coordinates within the space you are interested in.

I modified my placement of the display of a text to select buttons from:


[self.displayArea addSubview:choiceView];

to:
CGRect newFrame=[self.view convertRect:currentProblemDisplay.view.frame fromView:self.displayArea ];
currentProblemDisplay.view.frame=newFrame;
[self.view addSubview:currentProblemDisplay.view];

This way the view is displayed in the same location, but regardless of if it is in the bounds of the variable displayArea, it will still respond to inputs.