layoutIfNeeded causes a crash - ios

I have a UITextView, that is displaying text
When the text is not very large(i'm speaking about 100-200 words), the app is working just fine
If it has EXTREMLY large texts(around 10000 words), it keeps crashing
The code
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
check(scrollView)
let scrollPos = textView.contentOffset.y
if dragging { return }
if isAppearanceOpened { return }
if scrollPos > 0 {
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
if scrollPos <= self.contentOffset {
self.topView.alpha = 1
self.pageView.alpha = 1
self.topViewHeight.constant = 70
} else {
self.topView.alpha = 0
self.pageView.alpha = 0
self.topViewHeight.constant = 0
}
self.view.layoutIfNeeded()
}) { (_) in
if !decelerate { self.recheckEditorPosition() }
}
} else {
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
self.topView.alpha = 1
self.pageView.alpha = 1
self.topViewHeight.constant = 70
}) { (_) in
if !decelerate { self.recheckEditorPosition() }
}
}
}
The error
EXC_BAD_ACCESS (code=2, address=0x16d2a7ef0)
Console is clear, so i'm kind of confused
If i'm removing self.view.layoutIfNeedee(), the app works just fine, but the animation is absent

So, i got a result out of my research
The crash was caused by the textView contentInset.
When the left and right insets are 0, it's working well on large texts, and my guess is that the calculation of the textView alignements, fonts, and so on are quite time consuming, so the compiler just gave a error.

Related

Move scrollview automatically with page controller

Hi I am using the PageController in my application.
I have inserted all the data on scrollView.
What I want do is, I want to move the scrollview automatically like page control with specific time.
When the scrollView reaches to last page it again recall all pages speedy and start from first onwards.
I want to load/get first page very smoothly after last page completion.
Find my code with following.
var slides:[Slide] = [];
var offSet: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
slides = createSlides()
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
let timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
}
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(self.offSet)
}, completion: nil)
}
}
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.scrollView.scroll(topOffset:offSet, animated: true)
}, completion: nil)
}
}
extension UIScrollView {
// Bonus: Scroll
func scroll(topOffset:Float, animated: Bool) {
let ofSetValue = CGPoint(x: topOffset, y: 0)
setContentOffset(ofSetValue, animated: animated)
}
}
Use the following code working fine.
#objc func autoScroll() {
let totalPossibleOffset = CGFloat(slides.count - 1) * self.view.bounds.size.width
if offSet == totalPossibleOffset {
offSet = 0 // come back to the first image after the last image
}
else {
offSet += self.view.bounds.size.width
}
DispatchQueue.main.async() {
UIView.animate(withDuration: 0.0, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = CGFloat(self.offSet)
}, completion: nil)
}
}

How to Animate a UIView n number of times within a loop and track each animation event

UIView.animate(withDuration: 3.0, animations: {
// UIView.setAnimationRepeatCount(3.0)
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
}, completion: { finished in
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = -(self.vwObj.frame.size.width)
})
})
This is the code i am using for animating a UIView. What i am trying to do is to animate the UIView n number of times. That can be done using UIView.setAnimationRepeatCount(3.0), but the problem is i am having an array of different images which i want to show within the UiView for each animation. Anyone having any idea about how to do that
Use the animatedImage property of UIImage to animate multiple images inside a imageview.
ImageView.image = UIImage.animatedImage(with: myImages, duration: 1.0)
where myImages is array of images you want to animate.
Follow this link for details.
Edit: Definitely a very crude way, but it works.
Declared this at top of class.
let arrayColors = [UIColor.red , UIColor.green, UIColor.blue]
var animationCount = 0
I called animateView() in viewdidload
func animateView() {
self.vwObj.backgroundColor = arrayColors[animationCount]
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
}, completion: { finished in
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = 0
self.view.layoutIfNeeded()
}, completion: { finished in
self.animationCount = self.animationCount + 1
if self.animationCount < 3 {
self.animateView()
}
})
})
}
Insted of array of colors, you can use array of images to set imageview.image = arrayImage[animationCount]
Seems solved by the below code:
UIView.animate(withDuration: 3.0, animations: {
print ("Animation Started")
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
print ("BeginCounter --- (self.counter)")
}, completion: { finished in
UIView.animate(withDuration: 3.5, animations: {
self.leadng.constant = -(self.vwObj.frame.size.width)
print ("AnimationCompleted")
self.counter = self.counter + 1
print ("CompleteCounter --- \(self.counter)")
//self.displayAnimation()
if (self.counter == 3) {
self.timer?.invalidate()
self.counter = 0
}
})
})
& calling---- timer = Timer.scheduledTimer(timeInterval: 4.0, target: self, selector: #selector(ViewController.displayAnimation), userInfo: nil, repeats: true)

Animation not working in swift

I have a button and a collection view (horizontal) inside keyboard input view. Auto Layout is used. The button is hidden by default using Leading Constraint set to -50. When user start using collection view and contentOffset.x of collection view is greater than 80, The button will show. The code is working fine but animation is not working.
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.collectionView.contentOffset.x > 80 {
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseIn, animations: {
self.sideButtonLeadingConstraint.constant = 0
self.view.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseIn, animations: {
self.sideButtonLeadingConstraint.constant = -50
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
First, you shouldn't change you constraints inside animations block. Second, scrollViewDidScroll method is called a large number of times, and you should set some limitations for calling animation code inside it. Try something like this:
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let needsShow = collectionView.contentOffset.x > 80 && sideButtonLeadingConstraint.constant != 0
let needsHide = collectionView.contentOffset.x <= 80 && sideButtonLeadingConstraint.constant != -50
if needsShow {
sideButtonLeadingConstraint.constant = 0
} else if needsHide {
sideButtonLeadingConstraint.constant = -50
}
if needsShow || needsHide {
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
Try as follows by updating the constant outside the animation block. It will do the update with animation effect.
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.collectionView.contentOffset.x > 80 {
self.sideButtonLeadingConstraint.constant = 0
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
else {
self.sideButtonLeadingConstraint.constant = -50
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}}

How to reveal hidden text by sliding UITableViewCell

I'm trying to place a date field to the left of a cell in UITableView that is revealed when the user slides the cell from left to right. When released, the cell should spring back into place, hiding the date field. Does anyone know how to do this? Is it handled in
func tableView(_ tableView: UITableView, editActionsForRowAt: IndexPath) -> [UITableViewRowAction]? {
or defined somehow within a custom cell?
I wouldnt use the editing delegate as you posted.
Put the cells content in a UIView, in a UIScrollView, then setup the information behind the UIScrollView you wish to reveal.
Then on the scroll delegate, scrollViewDidEndDragging, set the contentOffset back to 0 animated.
EDIT: Pan gesture example
If you wish for the simplest route, setup your cell with a customContentView that is the size of your cell, add the pan gesture to this, then setup and buttons you want, in my example below I had a button each side.
Note: This is swift 2, but should be easily translatable, I made this a longgg time ago when swift 2 was released, it could do with refactoring
var savedX = 0 as CGFloat
var buttonWidth = 60 as CGFloat
var open = false
func panGestureHandler(gesture: UIPanGestureRecognizer) {
if gesture.state == .Changed {
let translation = gesture.translationInView(tagView)
let difference = -translation.x
if difference > 0 && !allowScrollRight {
return
}
let newConstant = savedX + difference
tagViewCenterXConstraint.constant = newConstant
let alpha = abs(tagViewCenterXConstraint.constant) / buttonWidth
deleteButton.alpha = min(alpha, 1)
followButton.alpha = min(alpha, 1)
if let action = swipe {
action(self)
}
}
if gesture.state == .Ended {
let translation = gesture.translationInView(self)
let trans = fabs(translation.x)
open = !open && trans > buttonWidth
if open {
if(translation.x > 0){
resetRight(true)
} else {
if allowScrollRight {
resetLeft(true)
}
}
} else {
resetView(true){
}
}
}
}
func resetLeft (animated : Bool) {
tagViewCenterXConstraint.constant = self.buttonWidth
savedX = self.buttonWidth
if animated {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.7, options: [UIViewAnimationOptions.CurveEaseIn, UIViewAnimationOptions.BeginFromCurrentState], animations: { () -> Void in
self.tagView.layoutIfNeeded()
self.leftView.layoutIfNeeded()
self.rightView.layoutIfNeeded()
}, completion: { (finished) -> Void in
})
}
}
func resetRight (animated : Bool) {
tagViewCenterXConstraint.constant = -self.buttonWidth
savedX = -self.buttonWidth
if animated {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.7, options: [UIViewAnimationOptions.CurveEaseIn, UIViewAnimationOptions.BeginFromCurrentState], animations: { () -> Void in
self.tagView.layoutIfNeeded()
self.leftView.layoutIfNeeded()
self.rightView.layoutIfNeeded()
}, completion: { (finished) -> Void in
})
}
}
func resetView (animated : Bool, completion: () -> Void ) {
tagViewCenterXConstraint.constant = 0
savedX = 0
open = false
if animated {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.7, options: [UIViewAnimationOptions.CurveEaseIn, UIViewAnimationOptions.BeginFromCurrentState], animations: { () -> Void in
self.tagView.layoutIfNeeded()
self.leftView.layoutIfNeeded()
self.rightView.layoutIfNeeded()
}, completion: { (finished) -> Void in
completion()
})
} else {
completion()
}
}

Swift - Shrink Navigation Bar on scroll

I'm really stuck here. I would like to shrink the Navigation Bar when I scroll down a UITableView and enlarge it again when scrolling up. I managed to alter the size of the Navigation Bar, but the title image is not shrinking with the Navigation Bar.
I want to do it exactly like Safari, and the problem is that the height of my TitleView is shrinking but the width never changes.
Here's the code I've used to get the height of the scrollbar to change.
func scrollViewDidScroll(scrollView: UIScrollView) {
var navbar = navigationController?.navigationBar
var dir:CGPoint = tableview.panGestureRecognizer.translationInView(self.tableview)
var scrollViewHeight = tableview.frame.size.height
var scrollContentSizeHeight = tableview.contentSize.height
var scrollOffset = tableview.contentOffset.y
if (dir.y > 0 && self.formernavstate == "small") {
self.formernavstate = "big"
UIView.animateWithDuration(0.5, delay:0.0, options: UIViewAnimationOptions.AllowAnimatedContent, animations: { () -> Void in
println("")
navbar?.frame.origin.y = 20
self.navigationItem.titleView?.transform = CGAffineTransformMakeScale(0.52, 0.6)
}, completion: nil)
}
if (dir.y < 0 && self.formernavstate == "big") {
self.formernavstate = "small"
navbar?.frame.origin.y = 0
navigationItem.titleView?.transform = CGAffineTransformMakeScale(0.0001, 0.2)
}
}
I implemented a Swift 3 version with a custom "header" and resizing it's height constraint based on #chauhan's answer:
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollViewOffset < scrollView.contentOffset.y {
shrinkHeader(shrink: true)
} else if scrollViewOffset > scrollView.contentOffset.y {
shrinkHeader(shrink: false)
}
}
func shrinkHeader(shrink: Bool) {
if shrink {
if self.headerContainerHeightConstraint.constant > CGFloat(minHeaderHeight) {
UIView.animate(withDuration: 0.1, animations: {
self.headerContainerHeightConstraint.constant = self.headerContainerHeightConstraint.constant - 2
self.view.layoutIfNeeded()
})
}
} else {
if self.headerContainerHeightConstraint.constant < CGFloat(maxHeaderHeight) {
UIView.animate(withDuration: 0.1, animations: {
self.headerContainerHeightConstraint.constant = self.headerContainerHeightConstraint.constant + 6
self.view.layoutIfNeeded()
})
}
}
}
}

Resources