I have a VC with a subview. At the subview level I have a button. I want the button to result in the view being updated.
The IBAction code is in the VC. All it currently does is toggle a BOOL value.
Back in the subview drawRect: method is the code that tests the BOOL and changes the view accordingly.
My observation is that when the button is pressed, the IBAction code runs, toggles the BOOL, but the drawRect: method doesn't get called.
I tried calling drawRect manually (not good practice and didn't work). I tried calling setNeedsDisplay but that too didn't work (couldn't figure out how to reference the subview, which is created programmatically).
Suggestions welcome.
IBAction code is trivial but included below:
- (IBAction)rotateWindowButtonWasPressed:(id)sender
{
// just flip the boolean here. Need to make the code change in drawRect: in top2View.m
if ([myGlobals gWinVertical])
[myGlobals setGWinVertical:NO];
else
[myGlobals setGWinVertical:YES];
// Hoping this would force a redraw, but bounds have to change...:(
//_top2ViewOutlet.contentMode = UIViewContentModeRedraw;
}
to issue a drawRect call, call setNeedsDisplay on the view you changed.. the OS will call drawRect on it and affected overlapping views automatically.
in your case I think
[self.view setNeedsDisplay]
Not sure what happened - there was an answer above that solved my issue but now it's gone? Anyway - the answer is to create the button programmatically and then I can include the method at the subview level where drawRect: is, and using setNeedsDisplay to repaint the screen. That vs. using IB and having the IBAction code at the VC level.
Related
I need to understand when UIView appears on the screen, so I need an analogue of the viewDidAppear method.
I found a UIView lifecycle:
willMoveToSuperview
invalidateIntrinsicContentSize
didMoveToSuperview
awakeFromNib
willMoveToWindow
needsUpdateConstraints
didMoveToWindow
setNeedsLayout
updateConstraints
layoutSubviews
drawRect
I tried all of these methods, but I didn't get an answer.
No there is no viewDidAppear in UIView. you may override func drawRect to do any UI changes that you need on UIView inherited View.
SideNote - In case you want get drawrect to update at later times, Call setNeedsDisplay. setNeedsDisplaywill not immediately call drawRect but marks the receiver’s entire bounds rectangle as needing to be redrawn.
In other words - You should never call drawRect yourself. Instead, you tell the system that drawing needs to be done by using the setNeedsDisplay method, which marks the view as dirty. And the drawRect method of the subclass would then be called during the next update cycle.
As per the queries from OP(#Alexander), he just need to set some variable so it advisable to use any of the following override functions, depending on action need to be performed
-(void)didMoveToSuperview - sent immediately after the view is
inserted into a view hierarchy.
-(void)didMoveToWindow - sent immediately after the view gets its
window property set.
-(void)willMoveToSuperview:(UIView *)newSuperview - sent
immediately before the view is added as a subview to another view;
newSuperview may be nil when you remove the view from its
superview.
-(void)willMoveToWindow:(UIWindow *)newWindow - sent immediately
before the view (or its superview) is added to a window; newWindow
may be nil when you remove the view from a window.
Look, viewDidAppear is method of UIViewController which represents moment when view of ViewController did appear and allows you to do declare what should happen.
UIView has no method like this. This comes from MVC pattern: controller is in this case UIViewController which controls changes, actions, etc., and view is just what controller shows.
I've been struggling with this for a couple of days now and haven't been able to pinpoint the cause of my problem, as the title says, a UIButton on a subview is not firing the IBAction outlet when "clicked".
Let me elaborate, I'm working on a "tinder-like" app, where I'm using this third party library. I'm implementing customized views which have buttons and other controllers in them. The buttons in these "Cards" are not firing their IBAction outlets.
As far as I can tell, the library does the following:
Stacks 3 "DraggableViews" on top of each other, each draggable view has 2 child views, one is the content view, where my custom view lives, and the other is an overlay view, which has an image view on top. The draggable views use two gesture recognizer to do its thing, the first one is a tap gesture that calls a delegate method to handle tap events in the card (in their example its used to show a browser view). The second gesture is a pan gesture used to implement the swipe functionality of the card.
I've done my homework and have tried a few different solutions but I can't get the IBAction to fire. I've tried the following:
Use the parent view of the DraggableViews to disable the gesture recognizer when the touch event is triggered on a button. - This works (gesture is disabled) but the IBAction is not fired. on the case of the pan gesture, I am no longer able to "swipe" the card when touching the button and the tap event does not hit a breakpoint in the delegate method mentioned above. In the UI, my button reacts to the touch as it animates, but its outlet in the view controller fails to hit a breakpoint. I've disabled this as so:
//- Called when each DraggableView is visible (not called for view at index 0, but works alright for debugging in the mean time)
func koloda(koloda: KolodaView, didShowCardAtIndex index: UInt) {
let subviews = koloda.subviews
for subview in subviews {
if let recognizers = subview.gestureRecognizers {
for gesture in recognizers {
if gesture is UIPanGestureRecognizer || gesture is UITapGestureRecognizer{
//- Also tried setting this to false but nothing.
gesture.cancelsTouchesInView = false
gesture.delegate = self
}
}
}
}
//- On the gestureRecognizerDelegate
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view is UIButton {
return false
} else {
return true
}
}
Implemented a tap gesture on the button programmatically, On viewDidLoad I add the action to the tap gesture but this does not trigger the outlet either.
Programmatically set the userInteractionEnabled property of the ImageView to True hoping that this will allow the touch event to go through the responder chain (although I read in another post that this would have the opposite effect?).
I've also checked in the storyboard that all the relevant views and controllers in my custom view have the userInteractionEnabled option enabled.
I don't know if its relevant, but my custom view lives in a xib file, when I pass the view to the library I do it by instantiating the view controller and passing passing over its view, as so:
if let vc = spiViewConntroller {
return vc.view
}
Any help or suggestions would be greatly appreciated!
Cheers!
EDIT:
Continuing with my search for truth, I've completely removed the overlay view from the library, inspecting my custom views on the UI debugger i can see that the overlay view and its ImageView are no longer there. The button still does not fire its outlet so I can assume that the overlay view is not causing this issue.
I've forked the Koloda library and created a branch with a demo example, the branch name is "StackOverflowDemo". I've added a custom view with one button, I've created two outlets in its view controller where I'm changing the title of the button (which works) on view did load. I've also disabled the two gestures on the button to replicate what i've currently got in my app. If you do clone this down you'll need to swipe the first card off as the card at index 0 wont have the gestures disabled.
I'll keep digging, hopefully someone can pinpoint what I'm doing wrong!
Cheers.
Daniel.
Your issue is that you don't add your view controller as child to view controller which operates with Koloda. In the result your vc.view is shown, because Koloda retains it, but nobody retains your view controller, so you are losing important lifecycle methods and it gets deallocated.
The approach your trying to use is called Container View Controller. Apple has suggestion about its implementation here: https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
Simple implementation here:
- (void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
}
OK, I found my answer. After digging some more bit i was able to solve this. However my solution feels dirty and I'm unsure if it breaks the MVC pattern.
I think my issue is that my custom View Controller for my xib file is somehow lost or unable to respond to these events. When creating the views to be displayed as the content of the draggable card, I was creating an instance of my view controller and returning its view. What's weird to me is that the ViewDidLoad method of the view controller was doing its job when the view was loaded (i.e. changing values of outlets).
Solution:
I removed the file owner from the lib, created a custom class that inherits from UIView, say MyCustomUIView and moving the view controller logic, its outlets and actions to that class. Then on the xib file, I linked the content view to MyCustomUIView. However this solution feels dirty as I'd expect the view's view controller to handle all the logic. Might need to do a bit more reading on nib and reusable views.
I can suggest a way, that can help you to detect the problem. Use the debugger view in xcode when the simulator is running. It will show you the hierarchy of views in 3-D mode, which can help you in finding out the issue.
How to use debuggin view:
https://developer.apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ExaminingtheViewHierarchy.html
I have two controllers with UITextFields on them. I need a custom transition between them with the keyboard shown, alongside transition.
When I'm using the default push animation, everything is good. But if I return my transition class from animationControllerForOperation, the keyboard is hiding before and showing after the transition.
So, could someone suggest to me a solution or explanation, why the keyboard is hiding with the custom transition? Transition duration = 0.4, I've set [textField becomeFirstResponder] in viewDidLoad.
Here is a video: https://www.youtube.com/watch?v=gs16tMsU4qY
Take a look at UIViewControllerTransitionCoordinator class.
Here is the answer to the similar issue: https://stackoverflow.com/a/21017900
EDIT
Or even an easier way:
implementing the delegate method - (BOOL) textFieldShouldEndEditing: (UITextField*) textFieldof UITextField and returning NO as long as the next VC is not loaded yet...
Perhaps you could try to override - (BOOL) canResignFirstRespondermethod of UITextField to return NO as long as the view of the new view controller is not loaded yet. (by setting a BOOL property for example, that is set to YES when the new view controller's view is loaded). like:
- (BOOL) canResignFirstResponder {
return nextVCIsLoaded;
}
perhaps that might prevent the keyboard from being hidden (that seems to happen automatically somewhere within the 'push' operation).
Hope this approach helps.
I'm trying to load a UIView and then right away detect touches on that new view. Currently, overriding touchesBegan gives a delay of around a second.
So, I load up the UIView and immediately keep tapping on the screen. It takes around a second for touchesBegan to be called. From that point on, all is well. However, I can't afford the ~second wait initially.
I have stripped back all the code in the UIView to just the barebones incase anything was clogging up the main thread, but the delay still persists.
How can I go about getting immediate feedback of touches from a newly presented UIView? Thanks.
-- EDIT BELOW --
I've been playing around with this for the past few hours. Even when creating a custom UIWindow and overriding sendEvent, the UITouchPhase gets halted when the new view is displayed. To begin receiving events again I have to take my finger off the screen and place it back on the screen. (I don't want to have to do this).
The problem seems to lie with the segue to the new view controller. When it segues, the touch phase is ended. If I simply add a subview to the current view controller, I see the desired functionality (i.e. instant responding to touch).
Given my newly presented view contains a lot of logic, I wanted to wrap it all up in it's own view controller rather than add it to the presenter view controller. Is there a way for me to do this and use 'addSubview` to present it? This should hopefully achieve the desired effect.
In the end, I created a custom view controller with it's own xib. Where I would have segued, I now instantiate that custom view controller and append it's view. This has eliminated the touch lag.
Have you disabled multi-touch? There's an inherent delay while the controller waits to see if there's a follow up touch (on all single touches). The initial sluggishness might be from loading up the multi-touch code and deciding what to do about it.
myViewController.view.multipleTouchEnabled=NO;
As to your final question, look into view controller containment. Since iOS 5 Apple has provided the hooks officially and safely to present one view controller as a sub view of another.
Sadly I've no insight as to the greater issue.
I found an answer that worked for me from a similar question asked here:
iOS: Why touchesBegan has some delay in some specific area in UIView
This solution isn't check-marked on that thread, so I'll copy it here to make it easier to find.
override func viewDidAppear(animated: Bool) {
let window = view.window!
let gr0 = window.gestureRecognizers![0] as UIGestureRecognizer
let gr1 = window.gestureRecognizers![1] as UIGestureRecognizer
gr0.delaysTouchesBegan = false
gr1.delaysTouchesBegan = false
}
I created regular buttons in .xib file and I added a gradient effect to them and shadows in the code in this section:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
... my customized buttons code here
}
When I modally switch to another view controller and then go back to the original one the xib file gets redrawn but all the gradient effects and shadows disappear. Any ideas?
I'm not sure entirely what's going on, but note that after you dismiss the modal view controller, viewDidAppear: gets called again. If you only want to make these buttons once, you could move your custom button code to viewDidLoad.
I haven't experienced this problem before to know what's going on, so it would be helpful to see the code itself. But I suspect that moving the code to viewDidLoad would solve the problem.
When modal View is dismissed, ViewDidLoad is not called, But ViewWillAppear and ViewDidAppear is called, But you say that the code is written in ViewDidAppear, and still it doesnt work. I suggest you to write that code in ViewWillAppear and check.