Swift PDFView jumps when frame is animated - ios

I have an app that has a fullscreen PDFView, and I'm trying to add an animatable tab bar on the top of the screen. However, any time I animate the PDFView's frame to show the tab bar, the PDF loaded in the view jumps to another position.
I've tried to animate with UIView.animate and with UIViewPropertyAnimator, and adding the PDFView as a subview and animating the parent, but all result in the same problem.
Has anyone encountered this problem? Any ideas on how to create the animation in a way that would result in a smooth user experience? The tab bar could obviously animate itself on top of the PDFView, but I don't like the idea of it covering the PDF.
Thanks!
func CreatePDFView() {
pdfView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
view.addSubview(pdfView)
}
func AnimatePDFView() {
UIView.animate(withDuration: 1) {
self.pdfView.frame = CGRect(x: 0, y: 40, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height - 40)
}
}

I've been looking to add this functionality for months, but haven't been able to solve this. As soon as I asked this question, though, it seems that I did.
You need to animate pdfView.documentView?.frame, and compensate for the current frame. I did this with UIEdgeInsets, and it seems to work.
if tabDocumentBrowserIsVisible {
tabDocumentBrowser.Animate(toState: .hidden)
UIView.animate(withDuration: 0.3) {
let inset = UIEdgeInsets(top: -80, left: 0, bottom: 0, right: 0)
self.pdfView.documentView?.frame = self.pdfView.documentView?.frame.inset(by: inset) as! CGRect
}
tabDocumentBrowserIsVisible = false
} else {
tabDocumentBrowser.Animate(toState: .visible)
UIView.animate(withDuration: 0.3) {
let inset = UIEdgeInsets(top: 80, left: 0, bottom: 0, right: 0)
self.pdfView.documentView?.frame = self.pdfView.documentView?.frame.inset(by: inset) as! CGRect
}
tabDocumentBrowserIsVisible = true
}
The PDF still behaves a bit funny if you are scrolling it while you animate it, but it's not too bad.

Related

Swift: Changing the hight of subview programmatically

I'm implementing a subview but I need to change the subview high programmatically but. This is how I'm changing the high:
func changeHigh() {
UIView.animate(withDuration: 1.0, animations: {
self.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height - 50)
self.buttonConstraint.constant = 10
})
}
But the problem with this implementation is animating also the position of the view. All I want is to change the high with out changing the position.
Any of you knows why is changing the position of the subview?
I'll really appreciate you help.
First, to change the height, you can just modify the size.height property of the frame.
self.frame.size.height -= 50 /// this will make it 50 pts less
Also, you should change constraints outside the animation block, not inside. Then, call layoutIfNeeded() to animate it.
Here's how your code should look:
func changeHigh() {
self.buttonConstraint.constant = 10
UIView.animate(withDuration: 1.0, animations: {
self.frame.size.height -= 50
self.layoutIfNeeded()
})
}

Swift || Bug when Animating and Removing Subview from Superview

Im made and DropDown Menu to select Action like the GIF below.
Therefore I made a Subview and animated it in.
When animating the Subview out, it looks really weird.
In particular the problem is that it just looks like a blank small bar and not like the Menu.
Does anyone know where the problem might be?
The ViewController I'm making the Subview of is a simple ViewController with a TableView inside and 1 prototype cell.
Code:
let blackView = UIView()
var tvx: OptionsVC = OptionsVC()
var h: CGFloat!
.
func optionsClicked() {
self.h = CGFloat(70 + (52 * (OptionsVC().arrayFunctionCellNames.count)))
navigationController?.hidesBarsOnTap = false
tvx = self.storyboard?.instantiateViewController(withIdentifier: "options") as! OptionsVC
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
view.addSubview(blackView)
view.addSubview(tvx.view)
let y = (self.navigationController?.navigationBar.frame.size.height)! + UIApplication.shared.statusBarFrame.height
tvx.view.frame = CGRect(x: 0, y: (y - self.h), width: view.frame.width, height: h)
blackView.frame = CGRect(x: 0, y: y, width: view.frame.width, height: view.frame.height)
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.tvx.view.frame = CGRect(x: 0, y: y, width: self.view.frame.width, height: self.h)
self.blackView.frame = CGRect(x: 0, y: (y + self.h), width: self.view.frame.width, height: self.view.frame.height)
}, completion: nil)
}
}
.
func handleDismiss() {
UIView.animate(withDuration: 5.5, animations: {
let y = (self.navigationController?.navigationBar.frame.size.height)! + UIApplication.shared.statusBarFrame.height
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.blackView.frame = CGRect(x: 0, y: y, width: self.view.frame.width, height: self.view.frame.height)
self.tvx.view.frame = CGRect(x: 0, y: (y - self.h), width: self.view.frame.width, height: self.h)
}
}, completion: {(finished:Bool) in
self.blackView.removeFromSuperview()
self.tvx.view.removeFromSuperview()
self.navigationController?.hidesBarsOnTap = true
})
}
GIF of BUG:
I made it so slow so you can better see it
Edit: My Solution
The problem was the constraints I set on my subview controller. Normally you set them to all sides, in my case, with was really weird I had to set them only to the bottom and sides. If I set some to the top, it would always do this bug.
You haven't really provided enough information for us to understand what's going on. What are tvx, blackView and self? What is the self.h variable? (From the animation it looks like you're changing the height of your "menu" view to be much shorter before you begin the animation code.)
Do you have a view controller contained inside another one using an embed segue?
If so, you should probably animate the constraints on the container view, not the child view controller's view.
As Glenn and D. Greg say in their comments, you should really be adding constraints to your views, hooking up outlets to those constraints, and animating changes to the constraint's constants rather than manipulating your view's frames directly. Animating changes to your view's frames isn't reliable when you're using AutoLayout, since AutoLayout can change your view's size and position out from under your animation code. That code looks like this, (in broad terms, no specific to your code)
myViewAConstraint.constant = someNewValue
myViewBConstraint.constant = someOtherValue
UIView.animate(withDuration: 5.5,
animations: {
someView.alpha = 0 //If you want the view to fade as it animates
self.view.layoutIfNeeded()
},
completion: { (finished:Bool) in
}
)

Unable to properly configure UIScrollView (Offset on top)

I have been fighting with this all morning and can't seem to find a solution. I have created a UIImageView, filled it with red, then added it to a UIScrollView and set the contentSize to the size of the UIImageView. If I print the contentOffset i see (0, 0) and if I print the contentSize and the UIImageView.frame.size they are the same but the red "image" always appears smaller than what the scrollView thinks the contentSize is.
If I scroll all the way to the top I see a cyan stripe about 100 pixels high above the red image and the scroll bar will not make it all the way to the top of what I believe the top of my scroll view to be. Although the top of the scroll bar does line up with the top of my red window so it would seem as though the scroll view is confused as to where it actually lives. Or more likely, I'm confused
Here is my what seems like very simple code...
imgHorizon = UIImage.init(named:"horizon")!
imgBezel = UIImage.init(named:"bezel_transparent")!
imgWings = UIImage.init(named:"wings_transparent")!
imgViewHorizon = UIImageView.init()
imgViewBezel = UIImageView.init()
imgViewWings = UIImageView.init()
svHorizon = UIScrollView.init()
super.init(coder: aDecoder)
imgViewHorizon = UIImageView(frame: CGRect(x: 0, y: 0, width: imgBezel.size.width, height: imgHorizon.size.height))
imgViewHorizon.backgroundColor = UIColor.red
imgViewBezel = UIImageView(frame: CGRect(x: 0, y: 0, width: imgBezel.size.width, height: imgBezel.size.height))
imgViewBezel.contentMode = UIViewContentMode.center
imgViewBezel.clipsToBounds = true
imgViewBezel.image = imgBezel
imgViewWings = UIImageView(frame: CGRect(x: 0, y: 0, width: imgBezel.size.width, height: imgBezel.size.height))
imgViewWings.contentMode = UIViewContentMode.center
imgViewWings.clipsToBounds = true
imgViewWings.image = imgWings
svHorizon = UIScrollView(frame: CGRect(x: 0, y: 0, width: imgBezel.size.width, height: imgBezel.size.width))
svHorizon.contentSize = CGSize(width: imgBezel.size.width, height: imgHorizon.size.height)
svHorizon.contentMode = UIViewContentMode.scaleToFill
svHorizon.bounces = false
svHorizon.backgroundColor = UIColor.cyan
svHorizon.contentOffset = CGPoint(x: 0, y: 0)
svHorizon.addSubview(imgViewHorizon)
addSubview(svHorizon)
addSubview(imgViewBezel)
addSubview(imgViewWings)
From the discussion in the comments it turns out that the Adjust Scroll View Insets option was checked in the attributes inspector of the ViewController. Unchecking it resolved the problem. Have a look at the image below. You need to uncheck the highlighted option.

Implementation closest to - push the same UIViewController

I am totally aware that you cannot push the same UIViewController from a UINavigationController. However my implementation demands so. I have to swipe gestures. On swiping right to left, I have to reload the content of view (which is a UIScrollView) along with animation. And vice versa on left to right swipe.
I have tried with following but it does not give the expected animation:
func leftSwipe()
{
self.scroll_course_detail.frame = CGRect(x: 2*self.scroll_course_detail!.frame.size.width, y: self.scroll_course_detail.frame.origin.y, width: self.scroll_course_detail.frame.size.width, height: self.scroll_course_detail.frame.size.height)
UIView.animate(withDuration: 0.25, animations: {() -> Void in
self.scroll_course_detail.frame = CGRect(x: 0, y: self.scroll_course_detail.frame.origin.y, width: self.scroll_course_detail.frame.size.width, height: self.scroll_course_detail.frame.size.height)
})
}
func rightSwipe()
{
self.scroll_course_detail.frame = CGRect(x: -self.scroll_course_detail!.frame.size.width, y: self.scroll_course_detail.frame.origin.y, width: self.scroll_course_detail.frame.size.width, height: self.scroll_course_detail.frame.size.height)
UIView.animate(withDuration: 0.25, animations: {() -> Void in
self.scroll_course_detail.frame = CGRect(x: 0, y: self.scroll_course_detail.frame.origin.y, width: self.scroll_course_detail.frame.size.width, height: self.scroll_course_detail.frame.size.height)
})
}
Before animation block, I am changing the frame of UIScrollView which makes the background clear for the animation duration. I was thinking that UIScrollView content is overriden along with animation
So what should be the best approach for reloading content along with animation on swipe ?

Slowing down UIScrollView scrolling

I'm using a UIScrollView to programatically animate some content.
However, I need to slow down the scrolling of the view.
This is the code I am using for the scrolling:
self.scrollView.setContentOffset(CGPoint(x: 0, y: self.view.frame.height), animated: true)
I have tried to add scrollView.decelerationRate = UIScrollViewDecelerationRateFast but it didn't seem to be working.
Try the following code:
UIView.animate(withDuration: 1.5) {
self.scrollView.contentOffset = CGPoint(x: 0, y: self.view.frame.height)
}

Resources