How To Implement Auto Scroll to TextView in Swift? - swift

I want to implement a program that lets the user click on a button and the textView should auto scroll to the bottom. I have tried many things available on the internet but not working as I want, I am new to Xcode, Please help...
This is what I have tried:
self.textView.isScrollEnabled = false
UIView.animate(withDuration: 12.0, delay: 0,options: ([UIView.AnimationOptions .repeat]), animations: {() -> Void in
self.textView.contentOffset = CGPointMake(0, self.textView.bounds.size.height)
}, completion: { _ in })
This is working but not as I want. It only scrolls the text which is on the screen not all the text.. please help..
I have also tried this:
scrollView.contentOffset = CGPointMake(0,0);
CGPoint point = textfield.frame.origin ;
scrollView.contentOffset = point
I got this from the internet but it does not work.

Firstly, bounds.size.height and frame.size.height here is just the size of your TextView in your parent view.
The thing you should notice here is the content inside your textView which is your contentSize. You just need to scroll to the end of your contentSize.height
Code will be like this
// scroll to the end of your content size height
self.textView.setContentOffset(CGPoint(x: 0, y: self.textView.contentSize.height), animated: true)

Related

Check if a collectionViewCell is showing more than 50% on collectionView

i got a UICollectionView with (3*3 Grid), so when i scroll up and down, is there a way i can detect if the collection view cell is displaying more than 50% of its height on the screen?
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.minX, y: visibleRect.midY)
let visibleIndexPath = collectionView.indexPathForItem(at: visiblePoint)
I have tried this but it doesn't seems to work. Does anyone has solution for this?
You can ask for
#property(nonatomic) CGPoint contentOffset;
of UIScrollView because UICollectionView's inherits from them.
and don't forget your Cell's have a CALayer that knows the visible Rectangle as well.
CGRect visiblerect = cell.layer.visibleRect;
if (visiblerect.size.height > (cell.frame.size.height * 0.5)) {
// do stuff when cell is more visible then half its size
}

iOS Swift - Position subview based on button frame (button is inside Vertical Stack View)

Update
After adding convert method between rect and main View, the Y position is ok, but X coordinate is shifted to the right outside of the main view:
Dropdown view(subview) is off main view
Below is button frame before and after convert method. Main view is 414 x 896. Dropdown menu somehow shifts to the right as on attached image.
button frame in stackView btnRect: (120.66666666666666, 0.0, 293.3333333333333, 30.0)
button frame in main view: cvtRect (241.33333333333331, 190.0, 293.3333333333333, 30.0)
view: Optional(>)
Goal. I want to make a dropdown list by showing UIView with dropdown options below a button. Button is inside of Vertical Stack View. I add a TableView with dropdown options to this dropdown UIView.I want to click a button and have this UIView with TableView inside to show just below the button. Basically following this tutorial https://www.youtube.com/watch?v=D3DCPaEE4hQ with the exception that my button is inside of Vertical Stack View.
Issue. UIView and TableView inside UIView show up ok when button is clicked. The issue is dropdown UIView's location that is always the same origin X=120, Y=0.
This is how I try to do it:
I have Vertical Stack with 4 rows
In 4th row I have label(width=120 almost same as X coordinate above) and a button that triggers UIView to show
I am using button to show dropdown list(basically UIView) that should appear just below button when the button is tapped, but it always appears at origin x=120 Y=0 , basically pinned to top of the right column in Vertical Stack View. Vertical Stack View has 1st column with labels, and second column with different controls like buttons etc.
func addTransparentView(frames: CGRect)
{
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
transparentView.frame = window?.frame ?? self.view.frame
//some of the stuff I tried to at least centre dropdownUIView
//transparentView.center.x = window?.center.x ?? self.view.center.x
//transparentView.center.y = window?.center.y ?? self.view.center.y
self.view.addSubview(transparentView)
tvPriority.frame = CGRect(x: frames.origin.x, y: frames.origin.y + frames.width, width: frames.width, height: 0)
//some of the stuff I tried to at least centre UIView
//tvPriority.center = verticalStackView.convert(verticalStackView.center, from:tvPriority)
//tvPriority.center.x = view.center.x
//tvPriority.center.y = view.center.y
self.view.addSubview(tvPriority)
tvPriority.layer.cornerRadius = 5
transparentView.backgroundColor = UIColor.black.withAlphaComponent(0.9)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeTransparentView))
transparentView.addGestureRecognizer(tapGesture)
transparentView.alpha = 0
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: UIView.AnimationOptions.curveEaseInOut, animations: {self.transparentView.alpha = 0.5
self.tvPriority.frame = CGRect(x: frames.origin.x, y: frames.origin.y + frames.height, width: frames.width, height: 193)
}, completion: nil)
}
To successfully make dropdown list I need UIView to show up just below buttons frame(X, Y, width, height). But although button is in the 4th row which should be position with much higher Y value, buttons frame is always at X=120, Y=0, so my UIView is always pinned to this location way above button that is supposed to simulate dropdown.
Questions
1. What am I missing with positioning of the dropdown UIView? Why is buttons position Y=0 when the button is in 4th row of Vertical Stack View, with obviously much higher Y position? I also tried to simply centre this dropdown in the centre of screen but that also does not work.
2. I transitioned to iOS development from the world of web development, and I used dropdown a lot in my career. Should I just use Picker View instead? Or alert? What is the most common and most standard way of offering list of mutually exclusive options to user in Swift app?
Thanks a lot
Your button is a subview of the stackView, so its frame is relative to the frame of the stackView.
To get its frame (rect) in the view's coordinate space, you'll want to use .convert. Assign this action to one of your buttons in the stackView:
EDIT Fixed the code example... I had not checked it before posting.
class ConvertViewController: UIViewController {
#IBOutlet var dropDownView: UIView!
#IBAction func didTap(_ sender: Any) {
guard let btn = sender as? UIButton else {
fatalError("Sender is not a button!")
}
guard let sv = btn.superview as? UIStackView else {
fatalError("Sender is not in a stackView!")
}
let btnRect = btn.frame
let cvtRect = sv.convert(btn.frame, to: view)
print("button frame in stackView:", btnRect)
print("button frame in main view:", cvtRect)
let dropDownRect = dropDownView.bounds
let cvtCenterX = cvtRect.origin.x + (cvtRect.size.width / 2.0)
let viewX = cvtCenterX - (dropDownRect.width / 2.0)
let newOrigin = CGPoint(x: viewX, y: cvtRect.minY + cvtRect.height)
dropDownView.frame.origin = newOrigin
}
}
If you look at the output in the debug console, you should see something like this:
button frame in stackView: (0.0, 114.0, 46.0, 30.0)
button frame in main view: (164.5, 489.5, 46.0, 30.0)
As you can see, the rect (frame) of my 4th button in my stackView has an origin of 0.0, 114.0, but after converting it to my view coordinate space, the rect's origin is 164.5, 489.5.
You can now position your "dropdown list" relative to the converted rect.
As a side note, you may want to look at UIPopoverPresentationController.

Swift contentOffset scroll

I am developing a small application for tvOS where I need to show a UITextView.
Unfortunately sometimes this text can't fit inside the screen, that's why I need a way to scroll to the bottom of it programmatically.
I've tried using the following code:
self.setContentOffset(offset, animated: true)
It's actually working as intended, except for the fact that I need to handle the animation speed, so that the user can actually read; at the moment it's too fast.
This is what I tried to do, but with little success:
let offset = CGPoint(x: 0, y: contentSize.height - bounds.size.height)
UIView.animate(withDuration: 4, animations: {
self.setContentOffset(offset, animated: false)
})
I need a way to keep the text in place and scroll it to show the missing lines.
I am using a UITextView inside an UIScrollView.
Thanks.
If anyone is looking for a solution, this is what I ended up using:
var textTopDistance = 100
//calculate height of the text not being displayed
textNotVisibleHeight = descriptionLabel.contentSize.height - scrollView.bounds.size.height
func scrollText() {
//start new thread
DispatchQueue.main.async() {
//animation
UIView.animate(withDuration: 6, delay: 2, options: UIViewAnimationOptions.curveLinear, animations: {
//set scrollView offset to move the text
self.scrollView.contentOffset.y = CGFloat(self.textNotVisibleHeight - self.textTopDistance)
}, completion: nil)
}
}
It's not perfect I know, but at least it's working as intended.

Swift: "Freeze" bouncing in a scrollView

I am trying to work a little bit with scrollViews. So I have a ScrollView on my Controller with bouncing enabled. So now then I scroll the view bounces at the end of the page like I want to.
But now I want to freeze my bouncing. I try to explain it a little bit more:
I scroll up in my ScrollView. The end of the page begins. So now the bouncing begins and its scrolling a little bit up. Then I undrag the scrollView the view bounces back. But I want the scrollView to stay in this position (because I push the view away and it looks weird when its bouncing while I push it).
Things I tried:
1.Set the frame on the scroll view to the current bouncing position. This just edits the frame but still bounces the scrollView.
v2.view.frame = CGRect(x: CGFloat(0), y: CGFloat(CURRENTBOUNCING), width: width, height: height)
2.Turn bounces off. This just ends the current bounce and after you can't bounce anymore. So if I don't want it to bounce back this doesn't work.
scrollView.bounces = false
3.Set contentOffset manually. Changes nothing for me even if I write it after initializing
scrollView.contentOffset.y -= 100 // Or something like this
4.Change the contentSize. This changes nothing I think because of the constraints
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height - 200)
5.Turn always bouncing vertically off. Same like 2.
scrollView.alwaysBounceVertical = false
6.Paging. I tried this because now I page manually. But then I miss the bouncing into nothing before paging. With paging you can see the next page while scrolling behind the end. But the next page should be loaded after dragging.
Hope someone can help me :)
EDIT
7.Taking a picture of my current scrollview to display it on my screen over the scrollview. This takes an image with an delay and not at the right moment ( so the bounces is at the half then capturing or already done.
let renderer = UIGraphicsImageRenderer(size: scrollView.bounds.size)
pufferImage.image = renderer.image { ctx in
scrollView.drawHierarchy(in: scrollView.bounds, afterScreenUpdates: true) }
Ran into the same problem. The solve is to record the content offset and change the vertical offset of the scrollview to that value while simultaneously turning scrolling off.
private var dismissTriggerd = false
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentOffset" {
if scrollView.contentOffset.y < -70 && dismissTriggerd == false {
scrollViewTop.constant = scrollView.contentOffset.y * -1
scrollView.isScrollEnabled = false
dismissTriggerd = true
dismiss(animated: true)
}
}
}
deinit {
scrollView.removeObserver(self, forKeyPath: "contentOffset")
}
I had a similar use case, the key was to switch the contentOffset into contentInset this negated the effect of the bounce back
maybe not a 100% complete solution, but i leave it here for inspiration
case UIGestureRecognizerStateEnded:
self.interactor.hasStarted = NO;
if (self.interactor.shouldFinish) {
UIEdgeInsets ei = self.scrollView.contentInset;
ei.top -= self.scrollView.contentOffset.y;
self.scrollView.contentInset = ei;
[self.interactor finishInteractiveTransition];
} else {
[self.interactor cancelInteractiveTransition];
}
break;
default:

UIScrollView in tvOS

I have a view hierarchy similar to the one in the image below (blue is the visible part of the scene):
So I have a UIScrollView with a lot of elements, out of which I am only showing the two button since they are relevant to the question. The first button is visible when the app is run, whereas the other one is positioned outside of the initially visible area. The first button is also the preferredFocusedView.
Now I am changing focus between the two buttons using a UIFocusGuide, and this works (checked it in didUpdateFocusInContext:). However, my scroll view does not scroll down when Button2 gets focused.
The scroll view is pinned to superview and I give it an appropriate content size in viewDidLoad of my view controller.
Any ideas how to get the scroll view to scroll?
Take a look at UIScrollView.panGestureRecognizer.allowedTouchTypes. It is an array of NSNumber with values based on UITouchType or UITouch.TouchType (depending on language version). By default allowedTouchTypes contains 2 values - direct and stylus. It means that your UIScrollView instance will not response to signals from remote control. Add the following line to fix it:
Swift 4
self.scrollView.panGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouchType.indirect.rawValue)]
Swift 4.2 & 5
self.scrollView.panGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirect.rawValue)]
Also, don't forget to set a correct contentSize for UIScrollView:
self.scrollView.contentSize = CGSize(width: 1920.0, height: 2000.0)
Finally I solved this by setting scrollView.contentSize to the appropriate size in viewDidLoad.
You need to add pan gesture recognizer. I learned from here: http://www.theappguruz.com/blog/gesture-recognizer-using-swift. I added more code to make it not scrolling strangely, e.g. in horizontal direction.
var currentY : CGFloat = 0 //this saves current Y position
func initializeGestureRecognizer()
{
//For PanGesture Recoginzation
let panGesture: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("recognizePanGesture:"))
self.scrollView.addGestureRecognizer(panGesture)
}
func recognizePanGesture(sender: UIPanGestureRecognizer)
{
let translate = sender.translationInView(self.view)
var newY = sender.view!.center.y + translate.y
if(newY >= self.view.frame.height - 20) {
newY = sender.view!.center.y //make it not scrolling downwards at the very beginning
}
else if( newY <= 0){
newY = currentY //make it scrolling not too much upwards
}
sender.view!.center = CGPoint(x:sender.view!.center.x,
y:newY)
currentY = newY
sender.setTranslation(CGPointZero, inView: self.view)
}