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.
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'm trying to replicate the animation of an app I currently use. In the app, after a picture is taken, if the user taps the UIImageView, it smoothly expands to a certain size, and upon tapping the UIImageView, it smoothly collapses to the original size.
Here is an example:
However, when I tried to replicate it, the animation is "wooply" as seen here:
Here's my code sample:
if recogizer.view == thumbnailImageView && isThumbnailExpanded == false
{
UIView.animateWithDuration(1, animations: {() -> Void in
self.thumbnailImageView!.frame.size.width = self.view.frame.size.width * 0.7
self.thumbnailImageView!.frame.size.height = self.view.frame.size.height * 0.6
self.thumbnailImageView!.frame.origin.x = (self.view.frame.width / 2) - (self.thumbnailImageView!.frame.size.width / 2)
self.thumbnailImageView!.frame.origin.y = -100
})
isThumbnailExpanded = true
}
else if recogizer.view == thumbnailImageView && isThumbnailExpanded == true
{
UIView.animateWithDuration(1, animations: {() -> Void in
self.thumbnailImageView!.frame.size.width = self.thumbnailImageViewWidth
self.thumbnailImageView!.frame.size.height = self.thumbnailImageViewHeight
self.thumbnailImageView!.frame.origin.x = self.thumbnailImageViewXPosition
self.thumbnailImageView!.frame.origin.y = self.thumbnailImageViewYPosition
})
isThumbnailExpanded = false
}
Here's my autolayout constraints:
How can I animate my UIImageView more smoothly as seen in above?
Thanks
The animation appears wobbly because you are changing the image's aspect ratio, you should maintain the aspect ratio so as not to squish the image.
Also, you should not animate a auto layout constraint-based UIView by changing its frame. Inside the animation block, you should change the image view constraints you added in the storyboard.
To get a reference to your storyboard created constraints, create an IBOutlet by control-dragging them to your view controller. This will create a variable of type NSLayoutConstraint in your code. Inside the animation block, change the constant property of each constraint you want to change and then call layoutIfNeeded on the view.
UIView.animateWithDuration(1, animations: {
yourConstraint.constant = 20
// any other constraints...
// ...
thumbnailImageView.layoutIfNeeded()
})
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.
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.
Is it possible to animate the frame width of a UISearchBar? I find when I apply uiview animations to widen the bounds of a search bar it pops immediately to the final result as if the object internally is assuming control of how it animates and not allowing me to apply my own animations to it smoothly.
If I animate the position it moves smoothly, but I suspect the fact that the text input adjusts according to the presence of the cancel button might mean we don't have public access to animate the width through UIView animation. The sample snippet below slides the bar from x = 0 to 100 but pops the width to 600 pixels wide.
CGRect searchBarFrame = self.searchViewController.searchBar.frame;
searchBarFrame.origin.x = 100;
searchBarFrame.size.width = 600;
[UIView animateWithDuration:1.0
delay:0.0
options:0
animations:^{
self.searchViewController.searchBar.frame = searchBarFrame;
}
completion:^(BOOL completion){
}];
there is an "issue" with UISearchBar due to the inner views forcing the resize to ignore the animation. However, this can be overcome by the use of - layoutSubviews. I have included the expand and contract code in my project below
[UIView animateWithDuration:.3
animations:^ {
CGRect newBounds = locationSearch.frame;
newBounds.size.width += 215; //newBounds.size.width -= 215; to contract
locationSearch.frame = newBounds;
[locationSearch layoutSubviews];
}];
Hope this helps.
FYI, you can use UIViewAnimationOption instead of calling layoutsubviews explicitly,
So the code would look something like this..
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionLayoutSubviews
animations:^{
//Set the frame you want to the search bar
}
completion:^(BOOL finished) {
}];
This is how to overcome enlargement just to the Left side in swift
(This code will enlarge/shrinks the searchBar 93 pixels over the left side when user start/end editing)
SharedNavigationbBar is the UIView that implements the UISearchBarDelegate
searchBarWidth is an outlet to a constrain holding the width of the UISearchBar
An autolayout constrain must exists on your storyboard or nib file to allow resizing into the left side.
In this case, the neighbord left component is an UIButton.
Add the following code as an extension or inside your class to perform the animated resizing.
extension SharedNavigationBar: UISearchBarDelegate
{
//amount of pixels to enlarge to the left
private var offsetSearchBarLeft:CGFloat
{
get {
return 93
}
}
///Enlarges search bar
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
self.animateSearchBar(self.searchBar, enlarge: true)
}
///Shrinks search bar
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
self.animateSearchBar(self.searchBar, enlarge: false)
}
//shrinks or enlarge the searchbar (this will be the function to call inside the animation)
private func animateSearchBar(searchBar:UISearchBar, enlarge:Bool)
{
///Important here, for this to work, the option and the searchbar size must be handled this way
UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.LayoutSubviews, animations: { [weak self] () -> Void in
let multiplier: CGFloat = enlarge ? 1 : -1
let origin = searchBar.frame.origin.x + self!.offsetSearchBarLeft * multiplier
let width = searchBar.frame.width + self!.offsetSearchBarLeft * multiplier
//This Block of code, setting the new frame, needs to be inside the animation in order to work
var newBounds:CGRect = searchBar.frame;
newBounds.origin.x = origin
newBounds.size.width = width
//Sets the new frame
self?.searchBarWidth.constant = width
searchBar.frame = newBounds
}, completion: nil)
}
}