Failed to find a presenting view controller - UIDatePicker in overlay - ios

I'm presenting an overlay view using the following code (by adding it as a subview to the window):
#objc func show() {
if let window = UIApplication.shared.keyWindow {
setupBlackView(window: window)
setupSelf(window: window)
window.addSubview(blackView)
window.addSubview(self)
UIView.animate(withDuration: 0.2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
if let height = self.percentageHeight {
self.frame = CGRect(x: 0, y: (1-height) * window.frame.height, width: screenWidth, height: screenHeight * height)
}
}, completion: nil)
}
}
The overlay view contains a UIDatePicker. This code used to work fine. However, now I am seeing the following error:
[Assert] Failed to find a presenting view controller for view (<_UIDatePickerIOSCompactView: 0x7fe8003288e0; frame = (0 0; 114 188.333); gestureRecognizers = <NSArray: 0x600003bca3d0>; layer = <CALayer: 0x600003025a80>>) in window (<UIWindow: 0x7fe7fc436010; frame = (0 0; 390 844); autoresize = W+H; gestureRecognizers = <NSArray: 0x6000038918f0>; layer = <UIWindowLayer: 0x60000367d5c0>>). The interaction's view (or an ancestor) must have an associated view controller for presentation to work.
and the UIDatePicker looks broken:

iOS 14 introduced the UIDatePicker instance properties datePickerStyle {get} and preferredDatePickerStyle {set get}. Your "broken" UIDatePicker appears to be one of the new styles. If you're expecting the "wheel" appearance, try setting the perferredDatePickerStyle property to UIDatePickerStyle.wheels. This may account for your error since a date picker compact view would be associated with the UTDatePickerStyle.compact style.

It seems like you want to build a DatePicker control that you can use everywhere in your application. What you can do is,
Create a derived class from UIWindow with higher Z-index, Check here
Create a derived class from UIViewController and add your datePicker control on with whatever UI customisations you like.
Next make the view controller you created in point no.2 as rootViewController of the window you created in point no.1
And now, make your window as keyWindow every time you want to show the datePicker. Since UIWindow is inherited from UIView class animate it the way you want and your awesome animating DatePicker is built from scratch.
P.S you can publish that as a CocoaPod as well. ;)

Related

Change UIWindow frame animated

Is there any way to change UIWindow animated while being visible and key?
I want to implement such behaviour when network connection disappeared. I want to squeeze content that is displayed at that moment a bit and add a notification between controllers and the status bar. Something similar to what happened in iOS 12 and before, when you have active call or geolocation usage.
I can't transform my controller, because it must be above transition stack in the iOS 13.
Or I should discuss it with designers if it completely impossible.
What I found that you can't change the frame after you inited and presented window by setting new frame. As well as setting Transform.
var frame = UIScreen.main.bounds
frame.origin.y = 60
frame.size.height -= 60
(UIApplication.shared.delegate as! AppDelegate).window?.frame = frame
(UIApplication.shared.delegate as! AppDelegate).window?.transform = CGAffineTransform(translationX: 0, y: 60)

I put UIView.animateWithDuration in viewWillAppear but it runs only during first entrance to the panel - why?

I try to animate a background in my swift ios app. I have an UIImageView with UIImage and I have the following method:
func slideImage(){
// Changes constant to be equal to the image width,
// this will move the image off-screen on the left-hand side.
backgroundImageConstraint.constant = backgroundPhoto.size.width
UIView.animateWithDuration(5, delay: 0, options: [.CurveLinear, .Repeat], animations: {
// Animates the constant change
self.view.layoutIfNeeded()
}, completion: {
done in
// Resets the image view back to its original position before starting a new round of the animation
self.backgroundImageConstraint.constant = 0
self.view.setNeedsUpdateConstraints()
})
}
I call this method in the viewWillAppear function, so I expected to run it every time when user enters my panel. However that's not how it works here - I see that it is invoked only once, when I enter the panel for the 2nd time the image in the background is not scrolling. This is a weird behavior. In my viewDidLoad I set all the things related to the UIImage:
override func viewDidLoad() {
super.viewDidLoad()
backgroundImage.image = backgroundPhoto
backgroundImage.frame = CGRect(x: backgroundImage.frame.origin.x, y: backgroundImage.frame.origin.y, width: backgroundPhoto.size.width, height: backgroundPhoto.size.height)
backgroundImage.contentMode = .Center
}
Does anyone knows why my photo is sliding only when user enters the panel for the first time and in other cases it is a static image?
Okay I'm gonna answer my own question - I found the information somewhere that if something interrupts the animations, the closure gets called automatically. So I moved calling the function slideImage() from viewWillAppear to viewDidAppear and now it works like a charm.
The animateWithDuration method is changing the frame of your background image moving it from the frame you set in viewDidLoad to the frame you have set (probably in your storyboard, with constraints). After your backgroundImage has been animated, it's frame changes and the next time you execute slideImage() method, the frame will be already changed, so it won't animate.
Try putting this two lines
backgroundImage.frame = CGRect(x: backgroundImage.frame.origin.x, y: backgroundImage.frame.origin.y, width: backgroundPhoto.size.width, height: backgroundPhoto.size.height)
backgroundImage.contentMode = .Center
before calling slideImage() in viewWillAppear and let us know the result.

Choppy animation in iOS

I have an animation in iOS with the following code:
view.layoutIfNeeded()
UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseInOut, animations: {
// constraints which are unrelated to drawingResultsController
self.leadingSpaceToContainerViewConstraint.constant = 0
self.trailingSpaceToContainerViewConstraint.constant = 0
// This controller doesn't have any constraints
drawingResultsController.view.frame.origin = CGPoint(x: self.view.bounds.width, y: 0)
self.view.layoutIfNeeded()
}, completion: { finished in
drawingResultsController.view.removeFromSuperview()
drawingResultsController.removeFromParentViewController()
drawingResultsController.didMoveToParentViewController(nil)
})
however, the animation does not look very fluid. It's not terrible by any means but you can definitely tell it is way under 30 fps. Does anybody have any idea as to why could this be? I have already verified everything is on the main thread.
Extra details:
The parent controller's view has a subview which is pinned to the edges of it's superview (the main view), those are the constraints seen above. This subview contains the main views of the controller (i.e. it's there to create a parallax effect as the drawingResultsController slides in and out of the screen). drawingResultsController is a child view controller added as a subview of the main view, and it doesn't have any constraints. It slides in and out of the screen, and this is the code to slide it out.
This way, the view moved by the constraints is a sibling of the view of the drawingResultsController. Both are direct subviews of the main view.
EDIT:
Reducing the animation to just this
UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseInOut, animations: {
drawingResultsController.view.frame.origin = CGPoint(x: self.view.bounds.width, y: 0)
}, completion: nil)
improves the frame-rate a little, but it continues to be an issue. It is more noticeable when setting longer animation times. The usage of CPU and memory look completely normal at the time of the animation.
Thank you
I had a similar issue, turns out it is solved by rebooting the device.
However the issue also seems to be solved by having a CADisplayLink updating a random layer that's part of your window's hierarchy. I noticed that, because we had an animation running somewhere in our app that was triggered by a CADisplayLink and that solved all slow animations elsewhere in the app!
The hack below solves the issue. Add the code to your main window class (assuming you use a custom UIWindow sub class for your main window):
//Hack for slow animation problem that occurs after long uptime of the device, updating a view's layer position that's in the view hierarchy using a display link solves the slow animations.
#property (nonatomic, strong) UIView *dummyView;
#property (nonatomic, strong) CADisplayLink *displayLink;
- (void)enableSlowAnimationHack {
if (self.dummyView == nil) {
self.dummyView = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:self.dummyView];
}
if (self.displayLink == nil) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(animateWithLink:)];
[self.displayLink addToRunLoop: [NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}
- (void)animateWithLink:(CADisplayLink *)sender {
self.dummyView.layer.position = CGPointMake((arc4random() % 100) / 100.0, (arc4random() % 100) / 100.0);
}
Apparently it solved on its own. It doesn't make much sense since it happened in both the simulator and the device, and no code was changed.

Display a subview in the centre of the display screen of i phone or ipad

I am designing an alert view in which I display a custom view when user taps a particular button.I am using a scroll view for my lengthy content view. I have written the code for the same as given below.
I want that this alert view to display right in the middle of your display screen irrespective of scroll position or content view position as alert in iOS by default pop ups. So is there any way to do that. I have tried a way but didn't succeed.
And the only solution I found is to scroll to top before displaying and add the subview. And since this is not perfect,so please suggest some measures or suggestions. Thanks.
#IBAction func forgotpassword(sender: AnyObject)
{
blurEffect = UIBlurEffect(style: .Dark)
blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = self.contentview.bounds
blurEffectView.autoresizingMask = [.FlexibleWidth,.FlexibleHeight]
self.AlertVieww.frame.size = CGSizeMake(blurEffectView.frame.width-40, self.AlertVieww.frame.height)//AlertVieww is the IBOutlet of my custom view in scene dock
AlertVieww.center = CGPointMake(UIScreen.mainScreen().bounds.size.width/2, UIScreen.mainScreen().bounds.size.height/2);
AlertVieww.autoresizingMask = [.FlexibleHeight,.FlexibleWidth]
blurEffectView.addSubview(self.AlertVieww)
[self.scrollview.setContentOffset(CGPointZero, animated: false)]//I scroll to the top
UIView.transitionWithView(self.view, duration: 1.0, options: UIViewAnimationOptions.TransitionFlipFromBottom, animations:
{
self.contentview.addSubview(self.blurEffectView)
}, completion: { finished in
self.scrollview.scrollEnabled = false
})
}
//These are two cnacel buttons in which I remove the views
#IBAction func sendAlert(sender: AnyObject)
{
self.blurEffectView.hidden = true
self.scrollview.scrollEnabled = true
}
#IBAction func cancelAlert(sender: AnyObject)
{
self.blurEffectView.hidden = true
self.scrollview.scrollEnabled = true
}
try with that
add your alert view in topmost controller's view instead of blurEffectView and set center of alertview isequal to topmost controller's view's center
like ,
AlertVieww.center = UIApplication.sharedApplication().keyWindow?.rootViewController?.view.center
UIApplication.sharedApplication().keyWindow?.rootViewController?.view.addSubview(self.AlertVieww)
UIView *subview = your View To Be Centered In Its SuperView;
UIView *superView = subview.superview;
subview.center = [superView convertPoint:superView.center
fromView:superView.superview];
If view is nil(on fromView:), this method instead converts from window base coordinates. Otherwise, both view and the receiver must belong to the same UIWindow object.
NOTE: If you use the auto layout stuff, then you have to change the constraints . not the frame or center
Here is a solution
Instead of self.view.frame.size.width
Use:: [UIScreen mainScreen].bounds.size.width or height
So code is
UIView *View = [[UIView alloc] init];
View.frame = CGRectMake([UIScreen mainScreen].bounds.size.width / 2, [UIScreen mainScreen].bounds.size.height / 2 , 18, 36);
[self.view addSubview: View];
here try it this one also to make cGpoint and according your main view:
view.center = CGPointMake(self.view.frame.size.width / 2,
self.view.frame.size.height / 2);
try this one...!
self.view.center = CGPointMake(CGRectGetMidX(self.parentView.bounds),
CGRectGetMidY(self.parentView.bounds));
remove this code
AlertVieww.center = CGPointMake(UIScreen.mainScreen().bounds.size.width/2, UIScreen.mainScreen().bounds.size.height/2);
Add this code
AlertVieww.center = self.blurEffectView.center
after
self.contentview.addSubview(self.blurEffectView)
One way is to create your own AlertController(inherits from UIViewController) and in its xib, put all the views for your alert along with proper constraint to be correctly shown on iPhone/iPad and whenever you need to show, Just present it on window like the following .
[window.rootViewController presentViewController:yourAlertControllerInstance animated:YES completion:nil];
OR
You can use this custom alert . It has variety of style along with BlurView in background and you can customise it as per your need.

setting frame in swift doesn't work as expected

I'm porting an app from Objective-C into pure Swift and I'm facing strange problem.
I've got AlertView class which is replacement for standard UIAlertView (now UIAlertController) for displaying animatable popups. AlertView is created in UIViewController extension - it's inited with view controller's view frame and added as a subview.
AlertView has a property which is a PopupView's instance - custom UIView subclass with xib (on Autolayout). This popup should has dynamic height depends on its contents (multiline message label).
Now when I'm trying to animate this popup in AlertView class:
when I set in PopupView setTranslatesAutoresizingMaskIntoConstraints(false) - view's height is correct but setting its frame in animation doesn't work as expected - view is sticked to the top left corner
when I set setTranslatesAutoresizingMaskIntoConstraints(true) - animation works as expected BUT view has a size from xib (won't expand according to contents)
What can be wrong here?
EDIT
Showing popup method:
private func showPopup(popupView: PopupView)
{
var beginFrame = popupView.frame
beginFrame.origin.y = -beginFrame.size.height
beginFrame.origin.x = self.bounds.size.width/2 - beginFrame.width/2
popupView.frame = beginFrame
var endFrame = beginFrame
endFrame.origin.y = self.bounds.size.height/2 - endFrame.size.height/2
popupView.hidden = false
DLog(beginFrame)
UIView.animateWithDuration(kAnimationTime, delay: 0, usingSpringWithDamping: kAnimationDamping, initialSpringVelocity: kAnimationSpringVelocity, options: UIViewAnimationOptions.CurveEaseIn, animations:
{ () -> Void in
DLog(endFrame)
popupView.frame = endFrame
}, completion: nil)
}
in both cases it shows in console:
(72.5, -155.0, 230.0, 155.0)
(72.5, 256.0, 230.0, 155.0)
EDIT2
setTranslatesAutoresizingMaskIntoConstraints(false)
setTranslatesAutoresizingMaskIntoConstraints(true)
Ok, got solution. I've stopped mixing autolayout and direct frame modifications and use pure autolayout instead.

Resources