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)
Related
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. ;)
I have a view that has a pan gesture recognizer on it to grow the overall height of the view as you swipe it up or down. This works well enough for now but I also wanted to add a drop shadow that I had designed and make sure that it kept with the height of the changing draggable view. However, this is producing some strange effects and I want to see if there is a way to fix this
The relevant code to handle changing the view height and the shadow height is as follows
// SET SHADOW FOR DRAGABLE VIEW PATH IN VIEW DID LOAD
draggableView.layer.shadowColor = UIColor.black.cgColor
draggableView.layer.shadowOffset = CGSize(width: 0, height: 12)
draggableView.layer.shadowOpacity = 0.33
draggableView.layer.shadowRadius = 8
draggableView.layer.shadowPath = UIBezierPath(roundedRect:
draggableView.bounds, cornerRadius: 12).cgPath
// INSIDE THE RECOGNIZER HANDLER
if recognizer.state == .began || recognizer.state == .changed {
translation = recognizer.translation(in: self.view)
recognizer.setTranslation(CGPoint(x: 0.0, y: 0.0), in: self.view)
let endPosition = recognizer.location(in: draggableView) // the posiion at which PanGesture Ended
difference = endPosition.y - startPosition.y
var newFrame = draggableView.frame
newFrame.origin.x = draggableView.frame.origin.x
newFrame.origin.y = draggableView.frame.origin.y + difference
// HERE WE SET THE HEIGHT OF THE VIEW THAT IS BEING "DRAGGED"
newFrame.size.height = draggableView.frame.size.height - difference
// HERE WE UPDATE THE SHADOW PATH WITH THE NEW DRAGGABLE VIEW BOUNDS
draggableView.layer.shadowPath = UIBezierPath(roundedRect: draggableView.bounds, cornerRadius: 12).cgPath
draggableView.frame = newFrame
}
The effect I'm getting is the shadow not quite updating in time with the current pan gesture, here is a gif of what that looks like
I'm hoping there is a way I can get this to work without this weird behavior, I'm fairly new to swift though I understand the concepts it's more about not really knowing all the best ways to go about doing things
You need to invert the order of the following two lines of code
draggableView.layer.shadowPath = UIBezierPath(roundedRect: draggableView.bounds, cornerRadius: 12).cgPath
draggableView.frame = newFrame
The reason is that you are updating the potision (Frame) of your draggableView in the PanGesture delegate method. However, you are updating the shadow frame (line 1) BEFORE updating the position (frame) of the draggable view (line 2). So, inverting the two lines will work for you
Suggestions:
In your case, you are updating frames of two views (draggable and shadow). We can see that the CPU is struggling trying to updating both of them since it's lagging (especially in the shadow part since you are re-drawing it.)
My suggestions is that you can create a parent transparent view and put your current draggable view inside and draw the shadow. This will happen only once in your viewDidLoad. Then inside gesture deletege, you will only update frame of the parent view. In this case, you do not need to update the shadow every time and the dragging will be more smooth
I'm trying to access the midX and midY properties of the bar button's frame to animate a view appearance from that point. To access them I'm using the following code:
let barButtonItem = self.navigationItem.rightBarButtonItem!
let barButtonItemView = barButtonItem.value(forKey: "view") as? UIView
let barButtonFrame = barButtonItemView?.frame
print ("FRAME", barButtonFrame)
As you can see, there is a print statement at the end of the chunk, and it prints:
FRAME Optional((0.0, 0.0, 46.3333333333333, 44.0))
For some reason, the frame coordinates are both at 0, so the view animation starts from the left top of the screen.
I've looked in many answers from here and almost all of them suggest the solution used above.
If you know why this happens, I would appreciate your help?
I have this weird problem, I call UIView.animateWithDuration in viewDidAppear of my app's initial View Controller, but the animation seems to jump (it becomes visible somewhere halfway). The only workaround I found is to add a delay before starting the animation. I'd love to understand why this happens and if there's a better solution than the delay.
To try it yourself, create a single view app, then add the method below in ViewController.swift (Xcode debugger slows the app a bit, to simulate real conditions, stop and launch the app from the device or simulator):
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let redSquare = UIView(frame: CGRectMake(0, 0, 50, 50))
redSquare.backgroundColor = UIColor.redColor()
redSquare.center = view.center
view.addSubview(redSquare)
UIView.animateWithDuration(0.5) { () -> Void in
var newFrame = redSquare.frame
newFrame.origin.y -= 200
redSquare.frame = newFrame
}
}
The red square is supposed to start translating from the center of the screen to somewhere up top, but it's as if the animation starts but the app is not ready to display it yet, so you see the red square starting it's translation above the center.
EDIT: Added call to viewDidAppear in example (does not solve the problem though).
I need someone to explain this to me as it is not making any sense.
When getting the UIKeyboards frame from the userInfo using UIKeyboardFrameEndUserInfoKey and doing the math so that a view would appear to be stacked on top of the keyboard, I need to make a difference of 20 pixels.
The math:
CGRect frame = view.frame;
CGPoint origin = frame.origin;
origin.x = kbFrame.origin.x;
origin.y = kbFrame.origin.y - view.frame.size.height - 20;
frame.origin = origin;
view.frame = frame;
I thought it must be the status bar, but here's the kicker, I'm developing on a retina display and so the status bar is 40 pixels in height not 20.
I then added a the conversion from view to view
CGRect kbFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIWindow * window = [UIApplication sharedApplication].windows[0];
kbFrame = [self.view convertRect:kbFrame fromView:window];
and that seems to fixed of it; that is I can remove the 20 pixels difference.
What really throws me is when using UIKeyboardFrameBeginUserInfoKey, the keyboard's frame is at the bottom of the screen, which is correct, but the end frame causes me to encode a 20 pixel difference. When I add the conversion code in, it puts the keyboard 20 pixels up and so gets rid of the difference. What the hell is going on?
The thing you are describing is the natural and wanted behaviour.
The thing is that the keyboard coordinate are in the Window Coordinate system.
Your view is probably not in Window Coordinate system. So you always need to do a conversion between coordinate system to be able to use it correctly.
What really throws me is when using UIKeyboardFrameBeginUserInfoKey, the keyboard's frame is at the bottom of the screen, which is correct,
I would disagree, you are probably 20 points lower than what you think you are (if you are setting it in your View coordinate system), but since that is off the screen you don't notice the offset.
Every View have it's own coordinate system that is inside it's bounds. And the frame of a view is expressed in it's parent coordinate system. That can also lead to some confusion if we don't understand the why and necessity of this difference.
I hope this will help you.
NOTE on Retina Display and Measurement :
On iOS you never deal with Pixel in your code (only when preparing your assets) you always deal with Point. So the Status bar is always 20 points, on Retina or not.