I have created a keyboard with some arrow keys that allows the user to navigate with arrow buttons, between all the text fields in the tableview. (2 per cell)
There are some situations where cells will be disabled, so I have a recursive call to skip them. ie. if the user presses the right arrow, and the textField is disabled, recursively call the right arrow again to skip it.
I also animate any scrolling that needs to be done myself. However, something has happened recently that is causing a EXEC_BAD_ACCESS when trying to set the firstResponder after a user navigates to a new textField, and the scrolling animation is finished.
I am able to reproduce this issue starting in the first cell and navigating down 2 times. Here is the code, in order of what is happening as best as I can display it, with a lot edited (like all the code for the other 3 arrow buttons so u only see the lines called)
//receive the arrow button pressed first
_arrowButton = arrowButton;
#try
{
switch (_arrowButton.tag)
{
case NumericKeyboardViewDownArrow:
destinationIndexPath = [NSIndexPath indexPathForRow:currentIndexPath.row + 1 inSection:currentIndexPath.section];
newTag = currentTextField.tag;
break;
default:
break;
}
// check bounds of the intended index path and return if out of bounds
if ((destinationIndexPath.row == -1) || (destinationIndexPath.row >= [_tableView numberOfRowsInSection:[destinationIndexPath section]])) {
destinationIndexPath = currentIndexPath;
return;
}
/*
Animation for scrolling
*/
CGPoint current = [_tableView contentOffset];
// if we must animate up
if (destinationIndexPath.row < currentIndexPath.row)
{
// up code here
}
// if we must animate down
else if (destinationIndexPath.row > currentIndexPath.row)
{
// always scroll animate for down
[_tableView setContentOffset:CGPointMake(0, current.y + _tableView.rowHeight) animated:YES];
}
// if we only move left or right
else
{
// left and right code here
}
//update our current location
currentIndexPath = destinationIndexPath;
#catch (NSException *e)
{
return;
}
then after the animation is finished in the did finish animating method
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
//after the animation is done, set the responder
nextTextFieldSelection = (UITextField *)[[_tableView cellForRowAtIndexPath:currentIndexPath] viewWithTag:newTag];
NSLog(#"nexttextfield%#",nextTextFieldSelection);
if (nextTextFieldSelection)
{
//CRASH OCCURS HERE [nextTextFieldSelection becomeFirstResponder];
}
[self checkTextFieldIsDisabled];
}
By the end of that method I am in a new text field and we check if disabled. In reproducing this crash, it just exits because the textFields I am moving in to are not disabled.
Ive tried resigning the first responder before calling a new becomeFirstResponder but that doesn't seem to have any effect. Its possible I wasn't resigning in the proper location though. Any ideas where the memory issue would be?
Thanks
Related
I have six button i want that user must select any of the six button before moving to next screen if it does not select any then it should not move to next screen I have read to use BOOL flag but this show which button is clicked i want that if any of the six buttons is clicked then user can move to next screen
-(IBAction)locationOneButtonAction{
// your stuff
_flag = YES;
}
-(IBAction)locationTwoButtonAction{
// your stuff
_flag = YES;
}
I think the best solution this problem is to declare a bool for your class (let's say it is called "ClickViewController" like (so at the top)
#interface ClickViewController () {
bool wasClicked;
}
...
-(IBAction)locationOneButtonAction{
// your stuff
wasClicked = YES;
}
Then when the button that should move to the next page is clicked utilize a method like this.
-(void)nextPageClicked{
if (wasClicked){
[self performSegueWithIdentifier:#"segueIdentifier" sender: self];
} else {
// do something else here, like tell the user why you didn't move them
}
}
This means that you cannot draw the segue from the button to the next view. If you are using storyboards, this means that you can't draw the segue from the button that is supposed to move it to the next view, rather you should draw it from the view controller icon to the next view. Then select the segue, name it, and use the method above with the name.
I'm also not sure if you mean that you want each of the buttons to move to the next view, if that is the case, you can use basically the same code, but just put the code [self performSegueWithIdentifier:#"segueIdentifier" sender: self]; line in action for the button.
Hopefully this helps.
Intially add BOOL _flag; int curIndex; in .h file.
Now add intially _flag = FALSE; in viewDidLoad method as it indicate no button clicked.
Make userInteractionEnabled for each button to be able to click and add tag to each button.
Now in action method of each click of button do this:
(IBAction)locationOneButtonAction:(id)sender{
curIndex = [sender tag]; //which button clicked
// your stuff
_flag = TRUE;
}
-(IBAction)locationTwoButtonAction{
curIndex = [sender tag]; //which button clicked
// your stuff
_flag = YES;
}
........
........
Now check like this if button clicked:
if(_flag)
{
NSLog(#"Button clicked with tag: %d",curIndex);
}
else
{
NSLog(#"Not any Button clicked");
}
I have a recursive call to perform some logged movements from user. While the animation is performed using the following recursive call, if user presses the back button to pop the viewcontroller out of the stack, somehow the animation doesn't stop and I can see the log entries (representing different log movements) on the console.
-(void)replayNextLog {
if ((replayLogIndex < [lastTurnGameLogList count]-1) && (!skipReplayButtonWasPressed)) {
float delayToNextReplay;
//the replayLogIndex ends with count-1 because the last one is empty due to the last empty line
NSDictionary *logEntryDict = [lastTurnGameLogList objectAtIndex:replayLogIndex];
for (id key in logEntryDict) {
if ([key isEqualToString:kGuessMoveTypeLetterButton]) {
} else if ([key isEqualToString:kGuessMoveTypeRevealHint]) {
} else if ([key isEqualToString:kGuessMoveTypeReset]) {
} else if ([key isEqualToString:kGuessMoveTypePass]) {
} else if ([key isEqualToString:kGuessMoveTypeShuffle]) {
}
}
replayLogIndex++;
// Recursive call
[self performSelector:#selector(replayNextLog) withObject:nil afterDelay:delayToNextReplay];
}
}
Could it be because of any leaks in the view controller? For your convenience, let me tell you that I am not reaching the dealloc method of the VC.
Thanks in advance.
Regards,
Obaid
You will need to control the animation/recursion, possibly using an instance variable, and start it from within the viewDidAppear method and stop it from the viewDidDisappear method. The view controller isn't necessarily destroyed just because it's popped from the navigation controller.
I am trying to animate 2 UIButtons in a UITableViewCell called addToPlaylist and removeFromPlayList (they animate off to the right after being swiped on) and am using a block as follows
[UIView animateWithDuration:0.25 animations:^{
self.addToPlaylist.center = CGPointMake(contentsSize.width + (buttonSize.width / 2), (buttonSize.height / 2));
self.removeFromPlaylist.center = CGPointMake(contentsSize.width + (buttonSize.width / 2), (buttonSize.height / 2));
myImage.alpha = 1.0;
}
completion:^ (BOOL finished)
{
if (finished) {
// Revert image view to original.
NSLog(#"Is completed");
self.addToPlaylist.hidden = YES;
self.removeFromPlaylist.hidden = YES;
self.hasSwipeOpen = NO;
}
}];
on completion I want to hide the buttons to attempt to lessen redraw on scroll etc.
This code sits within '-(void) swipeOff' which is called in the UITableViewControllers method scrollViewWillBeginDragging like so:
- (void)scrollViewWillBeginDragging:(UIScrollView *) scrollView
{
for (MediaCellView* cell in [self.tableView visibleCells]) {
if (cell.hasSwipeOpen) {
[cell swipeOff];
}
}
}
The problem is the completion code, if I remove it or set it to nil all is good, if I include it I get an EXC_BAD_ACCESS. even if I include it with any or all of the lines within the if(finished) commented out
Am I using this in the wrong way, any help much appreciated.
Thanks
I had the same problem with animations. I've solved it by removing -weak_library /usr/lib/libSystem.B.dylib from Other Linker flags.
Also, according to this answer, if you need this flag, you may replace it with -weak-lSystem.
Check if you are not calling a UIView (collectionView, Mapview, etc) from inside the UIView block, meaning, it would be a call outside the main thread. If you are, try this:
DispatchQueue.main.async {
self.mapBoxView.setZoomLevel(self.FLYOVERZOOMLEVEL, animated: true
)}
I have two textfields for username and password and a submit button. When the submit button is pressed a check is performed to see if the username and password was typed or not. If not it shows an alert message and the field whose value was not entered becomes the first responder.
-(IBAction)loginPressed:(id)sender {
if ([username.text length] == 0)
{
[self showAlert:#"Invalid Username/ Password"];
[username becomeFirstResponder];
return;
}
if ([password.text length] == 0)
{
[self showAlert:#"Invalid Username/ Password"];
[password becomeFirstResponder];
return;
}
}
I observed that on clicking the button, the button remains selected for about 1.5 seconds and then the alert is shown. If I comment out the becomeFirstResponder method, it works without any pause. However I need becomeFirstResponder to be there. How do I speed things up using this?
Switch the ordering of becomeFirstResponder and showAlert.
[self showAlert:#"Invalid Username/ Password"]; will take a time. you cant speeup that thing.
So I have a strange problem with my segmented control I am trying to use. Essentially I have a preferences panel that displays via a popover when a button is pushed.
The problem: I am trying to save the state so when the view loads, the segmented control should save it's selected item. Here is what I am doing so far...
-(void)viewWillAppear:(BOOL)animated {
if(!self.mainViewController.isThreaded){
self.threadedView.selectedSegmentIndex == 0;
//[self.threadedView setSelectedSegmentIndex:0];
//I can't do this because if I do it, it rexecutes the changeSegment method,
// which I do not want
}
if(self.mainViewController.isThreaded){
self.threadedView.selectedSegmentIndex == 1;
//[self.threadedView setSelectedSegmentIndex:1];
}
//threadedView.momentary = NO;
}
-(void)changeSegment {
if(self.threadedView.selectedSegmentIndex == 0){
self.mainViewController.isThreaded = NO;
[self.threadedView setSelectedSegmentIndex:0];
}
if(self.threadedView.selectedSegmentIndex == 1){
self.mainViewController.isThreaded = YES;
[self.threadedView setSelectedSegmentIndex:1];
}
}
now the problem is, when the popover appears, it does not load the state to the segmented control, as I understand it should. Can anyone point out what I may be doing wrong? Thanks
In viewWillAppear, if you want to set them and not test them it should be:
self.threadedView.selectedSegmentIndex = 0/1;
not
self.threadedView.selectedSegmentIndex == 0/1;, unless I'm missing something.