In my app i have a viewcontroller with a calendar in it (CKCalendar). Only one month is shown at the same time and with scrolling one can switch months. On certain days in the calendar are events which a highlighted by a circle.
Months are switched with the code
- (void) nextMonth:(UISwipeGestureRecognizer *)swipe {
[self.calView moveCalendarToNextMonth];
[self drawItemsForCurrentMonth];
}
The method drawItemsForCurrentMonth calls the next piece of code for every items that needs highlighting.
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
NSArray* res = [self calculateDayCirclePosition:date];
CGFloat x = [[res objectAtIndex:0] floatValue] + (DEFAULT_CELL_WIDTH-DEFAULT_CIRCLE_WIDTH-CELL_BORDER_WIDTH)/2;
CGFloat y = [[res objectAtIndex:1] floatValue] + (DEFAULT_CELL_HEIGHT-DEFAULT_CIRCLE_WIDTH)/2;
AgendaCircleView* circleView = [[AgendaCircleView alloc] initWithFrame:CGRectMake(x,y,self.circleWidth,self.circleWidth)];
circleView.date = date;
AgendaViewController* avc;
if([appDelegate.viewController.frontViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navVC = (UINavigationController*)appDelegate.viewController.frontViewController;
avc = (AgendaViewController*)[navVC visibleViewController];
}
UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:avc action:#selector(didSelectDate:)];
tapGesture.enabled = YES;
tapGesture.delegate = avc;
tapGesture.numberOfTapsRequired = 1;
tapGesture.numberOfTouchesRequired = 1;
[circleView addGestureRecognizer:tapGesture];
circleView.userInteractionEnabled = YES;
circleView.alpha = 0.5;
circleView.layer.cornerRadius = (self.circleWidth/2);
if(fill) {
circleView.backgroundColor = [appDelegate.dataController.rijschoolItem getColor1];
}
else {
circleView.layer.borderWidth = 1.0f;
circleView.backgroundColor = [UIColor clearColor];
circleView.layer.borderColor = [UIColor whiteColor].CGColor;
}
[circleView setNeedsDisplay];
[self.calendarContainer addSubview:circleView];
[self.calendarContainer bringSubviewToFront:circleView];
[self.calendarContainer setUserInteractionEnabled:YES];
This code works fine when the viewcontroller is initialized. Clicking on the circle fires the didSelectDate method. However when i change months the tapgesturerecognizer does not work anymore. When months are changed i use the exact same code that is used when initializing the viewcontroller. The strange thing is that when i open an other detailed viewcontroller and throw it on the view stack and go back, it is working again.
I've tried to use
gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer but this does not solve the problem.
Furthermore i've tried to use setNeedsLayout but this is not working either.
Any ideas?
Ok, I found the problem. So for others who might encounter the same type of problem. You have to make sure that the view is not animating while adding the gesturerecognizers.
I had this block of code
[UIView animateWithDuration:0.4 animations:^{
calendar.alpha = 1.0;
}];
[self drawItemsForCurrentMonth];
changed into
[UIView animateWithDuration:0.4 animations:^{
calendar.alpha = 1.0;
} completion:^(BOOL finished) {
if(finished) {
[self drawItemsForCurrentMonth];
}
}];
Having the method drawItemsForCurrentMonth after the animation block does not necessarily guarantee that the block is has actually finished. Luckily there is a completion block which covers that aspect.
Related
I have a fairly bog-standard UI with a text field embedded in a few subviews. I'm seeing an issue where I can't move the cursor once I've typed in the text field, it continually just moves back to the start of the input.
At no point anywhere in the code are any methods called to change the editing position, like setSelectedTextRange or similar.
Please excuse the Objective-C, this is a legacy codebase!
self.textField = [UITextField new];
self.textField.text = element.value;
self.textField.placeholder = element.placeholder;
self.textField.returnKeyType = UIReturnKeyNext;
self.textField.delegate = self;
self.textField.autocorrectionType = element.autocorrectionType;
self.textField.autocapitalizationType = element.autocapitalizationType;
self.textField.secureTextEntry = element.secureTextEntry;
self.textField.keyboardType = element.keyboardType;
[self.textField addTarget:self action:#selector(handleTextChange:) forControlEvents:UIControlEventEditingChanged];
[self addSubview:self.textField];
- (void)handleTextChange:(UITextField *)sender
{
self.inputElement.value = sender.text;
// If we're showing the validation warning, give real time feedback to user
if (self.isShowingValidationWarning) {
[self validate];
}
}
- (void)validate
{
BOOL isValid = self.inputElement.isValid;
[self showValidationHint:!isValid animated:YES];
}
- (void)showValidationHint:(BOOL)show animated:(BOOL)animated
{
self.isShowingValidationWarning = show;
CGFloat duration = 0.0;
if (animated) {
duration = 0.2;
}
[UIView animateWithDuration:duration animations:^{
if (show) {
self.characterCountLabel.alpha = 0.0;
self.validationButton.alpha = 1.0;
self.validationButton.transform = CGAffineTransformMakeScale(1.0, 1.0);
} else {
self.characterCountLabel.alpha = 1.0;
self.validationButton.alpha = 0.0;
self.validationButton.transform = CGAffineTransformMakeScale(0.1, 0.1);
}
}];
}
inputElement.value doesn't have a setter or getter function, so nothing funky going on there!
The answer here was exactly what #juanreyesv pointed out in the comments! becomeFirstResponder call was moved to viewDidAppear rather than viewWillAppear and now it's working!
I have simple view controller and I add this controller like subview for window using this code:
UIWindow *window = [UIApplication sharedApplication].keyWindow;
if (!window)
window = [[UIApplication sharedApplication].windows objectAtIndex:0];
self.view.alpha = 0.0;
self.view.userInteractionEnabled = YES;
[window addSubview:self.view];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)]];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
self.view.alpha = 1.0;
}completion:nil];
But when I click at this view - nothing happens. Even event touchesBegan: are not called.
UPD:
Code above is in -(void)show method. I want to show one controller above all controllers.
In FirstViewController I create instance of CustonAlertViewController like that:
CustomAlertViewController *alertVC = [[CustomAlertViewController alloc]init];
[alertVC show];
In CustomAlertViewController I have show method presented at the top and viewDidLoad method with:
self.view.backgrondColor = [UIColor greenColor];
Hidden views (and equivalently, those with alpha == 0.0) do not respond to touches. If you require a fully transparent view, leave alpha as > 0.0, and say...
self.view.backgroundColor = [UIColor clearColor];
Alternatively, assign a nonzero alpha.
EDIT yes, the CustomAlertViewController instance is being deallocated immediately after show is invoked. The view controller that does the allocating needs to have a strong property to keep the alert around,
#property(nonatomic,strong) CustomAlertViewController *alertVC;
and adding...
CustomAlertViewController *alertVC = [[CustomAlertViewController alloc]init];
self.alertVC = alertVC;
[alertVC show];
This doesn't try to address some potential problems beyond the scope of this question (like rotation, or cleanly restoring when the alert is done).
// try like this , u need to give number of touches
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)];
gesture.numberOfTapsRequired = 1;
gesture.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:gesture];
You have been setting your UITapGestureRecognizer after you addSubview to your window. like:
You have been doing this
//...
[window addSubview:self.view];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)]];
//...
Try this
Set UITapGestureRecognizer before you addSubview. And remember to add the UIGestureRecognizerDelegate to your ViewController
self.view.alpha = 0.2;
self.view.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)];
tapGesture.numberOfTapsRequired = 1;
[tapGesture setDelegate:self];
[self.view addGestureRecognizer:tapGesture];
// After you add your self.view to window
[window addSubview:self.view];
I hope this can help you.
I have a view called progressView that I have made draggable like this:
-(void)viewDidLoad
...
self.panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePanGesture:)];
[self.progressView addGestureRecognizer:self.panGesture];
...
-(void) displayPollingFor:(double)numOfSeconds
{
self.progressView.hidden = NO;
self.pollTimeLeftLabel.textColor = [UIColor whiteColor];
[self.view insertSubview:self.progressView aboveSubview:self.moviePlayer.view];
self.progressView.center = CGPointMake(self.view.frame.size.width - self.progressView.frame.size.width / 2 - 10, self.moviePlayer.view.frame.size.height / 2);
self.progressView.accessibilityElementsHidden = YES;
self.progressView.translatesAutoresizingMaskIntoConstraints = false;
[UIView animateWithDuration:4 animations:^{
self.progressView.alpha = .8;
self.progressView.hidden = NO;
}];
self.progressView.thicknessRatio = 0.2;
self.progressView.textColor = [UIColor blueColor];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(touchPollBtn:)];
// tap.cancelsTouchesInView = NO;
[self.progressView addGestureRecognizer:tap];
self.pollEndsLabel.hidden = NO;
self.minsSecsLabel.hidden = NO;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateLabel:)
userInfo:nil
repeats:YES ];
pollTimeLeft = pollTimeTotal = numOfSeconds;
[self updateLabel:nil];
}
-(void)handlePanGesture:(UIGestureRecognizer*)gesture
{
self.progressView.center = [gesture locationInView:self.view];
if (self.progressView.center.y > 100) {
self.progressView.center = CGPointMake([gesture locationInView:self.view].x, 100);
}
else if (self.progressView.center.y < 30) {
self.progressView.center = CGPointMake([gesture locationInView:self.view].x, 40);
}
}
This works fine in iOS7 and I can drag it and it will stay where I left it. in iOS8 the view always returns to the top left corner of the view. How can I fix this?
Thanks
self.progressView.translatesAutoresizingMaskIntoConstraints = true;
fixes this for iOS8. I don't know what changed between iOS7 and iOS8 to make this an issue though, so I'd be happy to have someone explain it still.
I am using a UIWebview,but there is some black background behind the web view in ios8. The same is working fine in ios7. I also set opaque to NO.
Try adding UIWebview as subview programmatically.
WebView = [[UIWebView alloc]initWithFrame:CGRectMake(264, 10, 745, 576)];
WebView.userInteractionEnabled = YES;
[self setBorderToView: WebView];
WebView.scrollView.bounces = NO;
[WebView setBackgroundColor:[UIColor clearColor]];
[WebView setOpaque:NO];
[WebView loadHTMLString: embedHTML baseURL: nil];
[self.view addSubview: WebView];
Hope this helps you!
I also faced same issue, after not getting proper solution I did a trick using Masking.
CALayer *aLayer = [CALayer layer];
aLayer.backgroundColor = [UIColor blackColor].CGColor;
aLayer.frame = CGRectMake(origin.x, origin.y, widthOfVideo, heightOfVideo);
objWebView.layer.mask = aLayer;
And This worked for me. Hope that can be helpful to you.
Put a instance variable in the in the header file or your class:
// instance variable for interval timer
NSTimer *BackgroundIntervalTimer;
Put these methods in the implementation file:
- (void)startUIWebViewBackgroundFixTimer {
// if > iOS 8 start fixing background color of UIWebPDFView
if (!SYSTEM_VERSION_LESS_THAN(#"8.0")) {
// hide pdfWebView until background fixed
self.pdfWebView.alpha = 0.0;
// start interval timer
BackgroundIntervalTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(fixBackground) userInfo:nil repeats:YES];
}
}
- (void)stopUIWebViewBackgroundFixTimer {
[BackgroundIntervalTimer invalidate];
BackgroundIntervalTimer = nil;
}
- (void)fixBackground {
// stop timer interval
[self stopUIWebViewBackgroundFixTimer];
// Assuming self.webView is our UIWebView
// We go though all sub views of the UIWebView and set their backgroundColor to white
UIView *v = self.pdfWebView;
while (v) {
//v.backgroundColor = [UIColor whiteColor];
v = [v.subviews firstObject];
if ([NSStringFromClass([v class]) isEqualToString:#"UIWebPDFView"]) {
[v setBackgroundColor:[UIColor whiteColor]];
// background set to white so fade view in and exit
[UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.pdfWebView.alpha = 1.0;
}
completion:nil];
return;
}
}
// UIWebPDFView doesnt exist yet so exit and try later
[self startUIWebViewBackgroundFixTimer];
}
Now put this line right after the line where you load the pdf:
// if iOS 8 check if pdfWebView got subview of class UIWebPDFView
[self startUIWebViewBackgroundFixTimer];
I have a simple program that creates a subview and animates it across the screen.
As part of this program, I would like to add functionality when the subview is tapped. I am using the following method to create the subview, add the UITapGestureRecognizer and then animate the subview:
int randomName = arc4random() % ([pieceNames count] - 1);
int animationDuration = arc4random() % 5 + 5 ;
NSString *randomPiece = [pieceNames objectAtIndex:randomName];
float yStart = arc4random() % 650;
float yEnd = arc4random() % 650;
UIView *piece = [[PieceView alloc]initWithFrame:CGRectMake(100.0, yStart, 75.0, 75.0)];
[piece setValue:randomPiece forKey:#"name"];
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc]initWithTarget:self
action:#selector(handleTouch:)];
[piece addGestureRecognizer:recognizer];
[[self view] addSubview:piece];
[UIView animateWithDuration:animationDuration
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^(void){
piece.center = CGPointMake(950.0, yEnd);
} completion:^(BOOL done){
[piece removeFromSuperview];
}];
Here is the code that handles the tap:
PieceView *pv = (PieceView *) recognizer.view;
NSLog(#"%# was tapped", pv.name);
What happens is when a PieceView is touched the program does not respond. However, if I remove the animation block then the program responds to the tap.
Why does the UITapGestureRecognizer fail to respond to PieceView when it is animated?
I struggled with this same problem, and it boils down to this: the animated view only ever is in two places: the starting position, and the ending position. Core Animation simply renders the view's layer in interpolated positions between the start and end points over a period of time.
It's almost like when you look to the stars and realize that what you see is not actually what is happening right now. :)
Luckily, the solution is pretty simple. You can put a tap recognizer on the superview and then inspect your animated view's presentationLayer (which does give you an accurate frame at any point in time) to determine if your tap is a hit or not.
I've built a simple UIViewController that demonstrates both the problem and solution:
#import <UIKit/UIKit.h>
#interface MSMViewController : UIViewController
#end
And the implementation:
#import "MSMViewController.h"
#interface MSMViewController ()
#property (nonatomic, strong) UIView *animatedView;
#end
#implementation MSMViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGRect startFrame = CGRectMake(125, 0, 70, 70);
CGRect endFrame = CGRectMake(125, 400, 70, 70);
// draw a box to show where the animated view begins
UIView *startOutlineView = [[UIView alloc] initWithFrame:startFrame];
startOutlineView.layer.borderColor = [UIColor blueColor].CGColor;
startOutlineView.layer.borderWidth = 1;
[self.view addSubview:startOutlineView];
// draw a box to show where the animated view ends
UIView *endOutlineView = [[UIView alloc] initWithFrame:endFrame];
endOutlineView.layer.borderColor = [UIColor blueColor].CGColor;
endOutlineView.layer.borderWidth = 1;
[self.view addSubview:endOutlineView];
self.animatedView = [[UIView alloc] initWithFrame:startFrame];
self.animatedView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:self.animatedView];
[UIView animateWithDuration:10 delay:2 options:UIViewAnimationOptionAllowUserInteraction animations:^{
self.animatedView.frame = endFrame;
} completion:nil];
// this gesture recognizer will only work in the space where endOutlintView is
UITapGestureRecognizer *boxTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(boxTap:)];
[self.animatedView addGestureRecognizer:boxTap];
// this one will work
UITapGestureRecognizer *superviewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(superviewTap:)];
[self.view addGestureRecognizer:superviewTap];
}
- (void)boxTap:(UITapGestureRecognizer *)tap {
NSLog(#"tap. view is at %#", NSStringFromCGPoint(self.animatedView.frame.origin));
}
- (void)superviewTap:(UITapGestureRecognizer *)tap {
CGRect boxFrame = [self.animatedView.layer.presentationLayer frame];
if (CGRectContainsPoint(boxFrame, [tap locationInView:self.view])) {
NSLog(#"we tapped the box!");
}
}
#end
The solution is much simpler, you just need to set the animation's option UIViewAnimationOptions.allowUserInteraction.
UIView.animate(withDuration: duration, delay: 0.1, options: [.allowUserInteraction], animations: {
...
}, completion: { (completed) in
...
}