NSTextView Scroll to bottom only if currently scrolled to the bottom? - swift

I'm trying to figure out how I can scroll to bottom and only scroll to bottom if the scroll position is at the bottom, like XCODE.
I can't figure out how I can get the current scroll position of an NSTextView.
It's simple to just scroll to the bottom using:
self.tbLog.scrollToEndOfDocument(nil)
But this happens every time and if you are scrolled up to the top of a log file and a new line is added, it drops back to the bottom. I only want to drop to the bottom if the scroll position is at the bottom.
SOLUTION:
// Add your text to the NSTextView here... then...
print("Position: \(NSMaxY(self.tbLog.visibleRect) - NSMaxY(self.tbLog.bounds))")
if (NSMaxY(self.tbLog.visibleRect) - NSMaxY(self.tbLog.bounds) == 0.0) {
print("we're at the bottom, keep at the bottom.")
self.tbLog.scrollToEndOfDocument(nil)
} else {
print("we're scrolled up somewhere... do nothing.")
}

Related

UITextView Moves to Left on Keyboard Show

I have an app with a build target of IOS 14 that is causing a problem regarding automatic positioning of the view on keyboard show.
I have a UITextView that is draggable and can be positioned partially outside of the main view that it sits within. If the field is large enough then it will extend beyond the parent view and safe area also. The parent view has clipsToBounds set as true so the overflow of the text view is not visible.
The problem is when the text field is positioned so that its right hand side is outside of the safe area and the keyboard is presented, the screen automatically scrolls left to include the far right edge of the text view, even though it is not visible due to clipsToBounds being set on its parent. I need to disable the behaviour that is causing this to happen but can't find anything that covers this for UIKit.
See below for a visual example. Can anybody please help?
Image 1
Image 2
Edit:
The structure of the screen is:
View Controller:
.....UICollectionView:
..........UICollectionViewCell:
...............UIView:
....................Elements (UITextView in this case)
func calculateCarouselOffset(formHeight: CGFloat) -> CGAffineTransform {
let carouselOffset: CGAffineTransform!
let currentElementMaxY = returnCurrentElementMaxY()
let elementMaxYTransformRemoved = currentElementMaxY + -self.scalingCarousel.transform.ty
let newFormOriginY = safeAreaFrame.height - formHeight
let topOfFormMargin: CGFloat = 20
if (newFormOriginY - topOfFormMargin) < elementMaxYTransformRemoved {
// Form will overlap element - move carousel view to compensate
let oldToNewLocDist = (newFormOriginY - topOfFormMargin) - currentElementMaxY
let moveScreenBy = self.scalingCarousel.transform.ty + oldToNewLocDist
carouselOffset = CGAffineTransform(translationX: 0, y: moveScreenBy)
} else {
// Form will not overlap element - reset carousel view
carouselOffset = self.formDeactivate
}
return carouselOffset
}
And it is called as below:
func textViewDidChage() {
let backgorundTransform = calculateCarouselOffset(formHeight: currentElementFormHeight)
let modifyBackground = UIViewPropertyAnimator(duration: 0.2, curve: .linear, animations: {
self.scalingCarousel.transform = backgorundTransform
})
modifyBackground.startAnimation()
}
It looks like this is (possibly new?) built-in behaviour for text fields. I reproduced this both with a collection view controller and a view controller holding a collection view. The text field moves itself to visible like this:
I found this by adding a symbolic breakpoint on contentOffset and then making a field editable - there are a lot of calls before you get to this point because it's also adjusting things for the keyboard coming up.
Unfortunately in your case, I think the scroll view is moving the text field's visible bounds into the visible area, which means you're scrolling horizontally since the text field is off screen.
You can't override scrollTextFieldToVisibleIfNecessary as it is private API. There are probably some hacks you can do by overriding becomeFirstResponder but they seem quite likely to either not work, or break other things.

How to scroll UICollectionView cells before animated insert of new cell?

I have a horizontal scrolling UICollectionView to which I want to add multiple cells that will cause a 2 stage animation to occur:
UICollectionView scrolls existing cells to the left, showing
the empty space where the new cell will appear, then...
The new cell to be inserted then scrolls up vertically from off screen into the empty space.
Imagine the Cells as cards appearing from the bottom of the screen when a user triggers an action.
Currently I have a custom UICollectionViewFlowLayout with an overridden initialLayoutAttributesForAppearingItem which will animate the scroll up element of the transition, however after the first item this animation occurs offscreen. I cannot figure out how to scroll the UICollectionView to the left to show the space where the future cell will appear before the slide up animation occurs.
I have dabbled with contentOffset changes, however this results in some very jerky and glitchy behaviour.
Whats the best way to approach this?
Current `initialLayoutAttributesForAppearingItem' implementation:
override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attr = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)
attr?.alpha = 0.0
attr?.transform = CGAffineTransform(
translationX: 0,
y: self.collectionView!.bounds.height
)
return attr
}

Add constraint on view

I would like to know how can I fix the height of a view so it can take full height of the device screen (iPhone only) minus the height of the tabar.
well I would click the UIView in question then towards the bottom right there is an icon looks like |o| (kind of)....its the pin menu
tap that
and make sure the top and bottom are solid orange...not the faded semi dotted orange and then put 0 in each of them
what do you mean by "tabar" ...are you referring to the status bar where the time and wifi signal are etc?
Response to comment
if you add the navigation bar first, then add the view and physically drag the bounds to the top left top right bottom left and bottom right and then do what i said it should have desired effect
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let rect : CGRect = (self.navigationController?.navigationBar.frame)!
let y = rect.size.height + rect.origin.y
self.view.contentInset = UIEdgeInsetsMake(y, 0, 0, 0)
}
This is the best example I can provide, since you didn't specify much. This adjusts the bounds of view if the view controller has a navigation bar
You can also apply layout constraints (auto resizing) which are in the inspector menu.
Apply all 4 side constraints to make your view always full size for all the devices.

How to programmatically add textField to a scroll view

I have a button on my storyboard. This button is inside a view and that view is inside a scroll view.
What I am trying to do sounds relatively simple: When the button is pressed, the button moves down a little bit and a textField appears where the button had previously been.
However, what happens is the button animates, but then returns to its original position, instead of staying moved, and the textField appears as it should.
Because the position of the appearing textField is dependent on the position of the button, pressing the button multiple times simply places a bunch of textfields on top of each other.
Here is a picture of what happens:
my Screen:
Here is the code I am running when the button is pressed:
#IBAction func addIngredientButtonPressed(sender: UIButton) {
contentView.bounds.size.height += addIngredientLabel.bounds.height
scrollView.contentSize.height += addIngredientLabel.bounds.height
//Create Text Field and Set Initial Properties
let newIngredientTextField = UITextField(frame: CGRectMake(self.addIngredientLabel.frame.minX as CGFloat, self.addIngredientLabel.frame.midY as CGFloat, self.addIngredientLabel.bounds.width,self.addIngredientLabel.bounds.height * 0.60))
newIngredientTextField.font = UIFont.systemFontOfSize(15.0)
newIngredientTextField.placeholder = "ADD INGREDIENT HERE"
newIngredientTextField.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left
newIngredientTextField.alpha = 0.0
scrollView.addSubview(newIngredientTextField)
UIView.animateWithDuration(0.5) { () -> Void in
self.addIngredientLabel.center.y += self.addIngredientLabel.bounds.height
newIngredientTextField.alpha = 1.0
}
}
Note:** the length is also being added successfully to the screen, but the button is not staying moved. So in the screenshot above you can indeed scroll up and down **
I noticed that if you comment out the top 2 lines of code, the ones that add height to the contentView and the scrollView, you get almost the right behavior. The button moves and stays moved, the textField appears in the right spot, but if you keep pressing, it won't let you add more textFields than the screen size will hold.
Here is a picture of what happens if you comment out the top two lines. The button won't do anything more if you keep pressing it more:
screen filled:
I found lots of tutorials that tell you how to add more content than can fit into one screen using the storyboard and setting the inferred metrics to free form, but I have not found anyone trying to add elements dynamically like I am.
You appear to have a contentView -- presumably a direct subview of your scrollView, but you're adding the newIngredientTextField directly to your scrollView rather than to your contentView.
When you increase the bounds.size.height of the contentView, it decreases its frame.origin.y by half as much (to keep its center in the same place). As your original button is presumably a subview of this contentView, that will cause it to move independently of the newly-added text field.
In other words, write:
contentView.frame.size.height += addIngredientLabel.bounds.height
rather than
contentView.bounds.size.height += addIngredientLabel.bounds.height
and
contentView.addSubview(newIngredientTextField)
rather than
scrollView.addSubview(newIngredientTextField).

Add an animation to swipe gestures in Swift

I have a swipe gesture in my app that when you swipe, it changes the text on screen. How would I make it so it looks like it is sliding to the new text rather than just instantly changing the text?
You could do one thing!!!add a view behind your gesture view ,that view should look same as that of gesture View(should even have same text),and once after recognizing gesture bring that view in-front of your gesture view it should have old text(don't update the text) and update your gesture view's text ones it moves behind newly added view, just change the frame of newly added view so that it gives a sliding kind of effect(change its width) once after the completing animation bring that view back to gesture view and change its frame to previous its previous value.
check this sample:
func handleGesture(sender:UISwipeGestureRecognizer) {
//bring background view to front
self.view.bringSubviewToFront(self.backGroundView)
self.gestureView.userInteractionEnabled = false
//capture its initialAutolayout constraint
let backgroundViewSpaceConstarint = self.backGroundViewLeftSpaceConstraint.constant
UIView.animateWithDuration(2, animations: { [unowned self]() -> Void in
self.someRandomValue++
// update text label in gesture view which is behind background View
self.gestureViewTextLabel.text = "swiped \(self.someRandomValue) times"
//slide backgroundView in required direction by using autolayout
self.backGroundViewLeftSpaceConstraint.constant = self.backGroundView.bounds.size.width
self.backGroundView.layoutIfNeeded()
}) { [unowned self](completion:Bool) -> Void in
//after animation complition bring gesture view to the front.
self.backgroundTextLabel.text = "swiped \(self.someRandomValue) times"
self.gestureView.userInteractionEnabled = true
//upadte background view so that it will look the same for next swipe
self.backGroundViewLeftSpaceConstraint.constant = backgroundViewSpaceConstarint
//send the background view behind gesture view
self.view.sendSubviewToBack(self.backGroundView)
}
}