I am making a an app that has a UITextView and a button.
When I click the button some text will add in the UITextView.
But when clicking the button, I wan't to scroll down to the bottom of the text field so the user can see the last text added.
How to make the UITextView to scroll down to the bottom?
I tried:
int numLines = LogTextView.contentSize.height / LogTextView.font.lineHeight+1;
NSLog(#"%d",numLines);
NSUInteger length = self.LogTextView.text.length;
self.LogTextView.selectedRange = NSMakeRange(0, length);
but it will not work...
I also tried:
self.LogTextView.contentSize=CGSizeMake(length,0);
You can use the following code if you are talking about UITextView:
-(void)scrollTextViewToBottom:(UITextView *)textView {
if(textView.text.length > 0 ) {
NSRange bottom = NSMakeRange(textView.text.length -1, 1);
[textView scrollRangeToVisible:bottom];
}
}
SWIFT 4:
func scrollTextViewToBottom(textView: UITextView) {
if textView.text.count > 0 {
let location = textView.text.count - 1
let bottom = NSMakeRange(location, 1)
textView.scrollRangeToVisible(bottom)
}
}
Try this if you have problem on iOS 7 or above. See this SO answer.
- (void)scrollTextViewToBottom:(UITextView *)textView {
NSRange range = NSMakeRange(textView.text.length, 0);
[textView scrollRangeToVisible:range];
// an iOS bug, see https://stackoverflow.com/a/20989956/971070
[textView setScrollEnabled:NO];
[textView setScrollEnabled:YES];
}
With Swift 3
let bottom = self.textView.contentSize.height - self.textView.bounds.size.height
self.textView.setContentOffset(CGPoint(x: 0, y: bottom), animated: true)
Swift 5
extension UITextView {
func simple_scrollToBottom() {
let textCount: Int = text.count
guard textCount >= 1 else { return }
scrollRangeToVisible(NSRange(location: textCount - 1, length: 1))
}
}
// Usage
textView.simple_scrollToBottom()
Make a range, specifying encoding, to the last character, then scroll to that range
Something other than utf8 might be appropriate depending on your content
let range = NSMakeRange(self.textView.text.lengthOfBytes(using: .utf8), 0);
self.textView.scrollRangeToVisible(range);
You have to implement a delegate method. The code below checks whether a newline has been entered and, if so, scrolls to the bottom of the textView:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if ([text isEqualToString:#"\n"]) {
textView.contentOffset = CGPointMake(0.0, textView.contentSize.height);
}
return YES;
}
This works for me! :D
CGPoint bottomOffset = CGPointMake(0, self.textView.contentSize.height - self.textView.bounds.size.height);
[self.description1 setContentOffset:bottomOffset animated:YES];
As a generic approach for scrolling to bottom, it can be done on a UIScrollView.
extension UIScrollView {
func scrollToBottom() {
let contentHeight = contentSize.height - frame.size.height
let contentoffsetY = max(contentHeight, 0)
setContentOffset(CGPoint(x: 0, y: contentoffsetY), animated: true)
}
}
This will work on all descendants of UIScrollView like UITextView, UITableView etc..
textView.scrollRangeToVisible(NSRange(..<textView.text.endIndex, in: textView.text))
This solution does a couple of notable things slightly different:
Utilizes the String.Index interface (likely more performant than e.g. .count)
Uses a PartialRangeUpTo which avoids an explicit range start position, reducing the code to a clean one-liner
The Swift version of #Hong Duan answer
func scrollTextViewToBottom(textView: UITextView) {
if textView.text.count > 0 {
let location = textView.text.count - 1
let bottom = NSMakeRange(location, 1)
textView.scrollRangeToVisible(bottom)
// an iOS bug, see https://stackoverflow.com/a/20989956/971070
textView.isScrollEnabled = false
textView.isScrollEnabled = true
}
}
Related
hi I have an extension in the following code but it could not run for me :(
Actually I want to scroll my textView with a entered speed but I don't know how can I do this
extension UITextView {
func simple_scrollToBottom() {
let textCount: Int = text.count
guard textCount >= 1 else { return }
scrollRangeToVisible(NSRange(location: textCount - 1, length: 1))
}
}
I used it as self.akor_goster.simple_scrollToBottom() in viewdidload
First, you would not want to call this from viewDidLoad()...
At that point, auto-layout has not finished arranging everything in the view, frame and text view content is not yet ready.
Also, you don't want to start an animation yet, because the view has not yet appeared.
So, if you want this to happen automatically, put the call in viewDidAppear().
Second, you cannot control the scrolling speed when calling scrollRangeToVisible() -- but you can calculate the offset and animate that change with your desired speed.
Try this extension:
extension UITextView {
func timedScrollToBottom(duration d: Double) {
let y = contentSize.height - frame.size.height
// if y is less than zero, all the text fits, so no scrolling needed
guard y > 0 else {
return
}
// Duration is in seconds
UIView.animate(withDuration: d, animations: {
self.contentOffset.y = y
})
}
}
and call it like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.akor_goster.timedScrollToBottom(duration: 2.0)
}
I'm trying to get my app to move the view when the keyboard appears, and so far the results have been... mixed to say the least. I can get it to move, thing is it's either hard coded or only works partially.
I have multiple Textfields in my view, when I tap on them, sometimes depending on where my scroll is it get's hidden by the keyboard.
Now what I need my app to do is to move the view to see the textfield only if the active textfield is hidden by the keyboard.
My Hierarchy for the view goes like this :
So I have a Scroll view, and in the scroll View I have a UIView named ContentView, in the ContentView I have all my textfields and labels.
thing is, I can't hard code it since My app is universal, I need to have the keyboard move the view only if it hides the textfield. Because in a situation where the user is on an iPad, the View will likely never have to move
I used the following Stack overflow answers with no results :
Swift: Scroll View only when a TextField or Button is hidden by the Keyboard
Move view with keyboard using Swift
here's my code that actually comes from one of those answers :
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(NewPaxController.keyboardWillShow), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(NewPaxController.keyboardWillHide), name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification:NSNotification) {
if keyboardIsPresent == false {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.ContentView.frame.origin.y -= keyboardSize.height
keyboardIsPresent = true
}
}
}
func keyboardWillHide(notification:NSNotification) {
if keyboardIsPresent == true {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.ContentView.frame.origin.y += keyboardSize.height
keyboardIsPresent = false
}
}
}
I'm almost 100% sure all my error come from the fact the I have a ContentView... but I need it in my case. Thanks in advance for your help
You should not modify frames when keyboard shows up.
Instead you need to set the bottom inset of the scrollview scrollView.contentInset.bottom from zero to keyboard height. When the keyboard disappears, you set the inset back to zero.
This whole problem is solvable by mere 10 lines of code.
literally get the keyboard height from notification, store it ion a local variable in the class while KB is showing, and use the value to set insets in delegate callback/event handler methods.
The trick here is that setting nonzero insets will effectively scroll the scrollview together with the content for you up by and that will push the current textfield up as well.
You should first try to locate the UITextField instance by using the following code.
extension UIView {
func firstResponder() -> UIView? {
if self.isFirstResponder() {
return self
}
for subview in self.subviews {
if subview.isFirstResponder() {
return subview
}
}
return nil
}
}
In the keyboardWillShow: function decide if the textfield is visible or not if the keyboard comes up.
func keyboardWillShow(notification:NSNotification) {
if keyboardIsPresent == false {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue(),
let inputView = self.ContentView.firstResponder()
where inputView.frame.maxY > self.ContentView.frame.size.height - keyboardSize.height {
self.ContentView.frame.origin.y -= keyboardSize.height
keyboardIsPresent = true
}
}
}
Than only move the view back in the hide function if it was moved away.
Here is the code for it, It is pretty straight forward, but if you still need help with it, I will explain it further.
#pragma mark - Keyboard Observer events
-(void)keyboardWillShow:(NSNotification*)notification {
NSDictionary *info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
keyboardHeight = kbSize.height;
[self updateScrollViewPosition];
}
-(void)keyboardDidChange:(NSNotification *)notification {
NSDictionary *info = [notification userInfo];
CGSize kbSizeBegin = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGSize kbSizeEnd = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
if (kbSizeBegin.height != kbSizeEnd.height) {
keyboardHeight = kbSizeEnd.height;
if (activeTextField && [activeTextField isFirstResponder]) {
[self updateScrollViewPosition];
}
}
}
-(void)keyboardWillHide:(NSNotification*)notification {
keyboardHeight = 0;
activeTextField = nil;
[self resignAllTextFields];
}
#pragma mark - UITextFieldDelegate Methods
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
activeTextField = textField;
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
activeTextField = textField;
[self updateScrollViewPosition];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
keyboardHeight = 0;
activeTextField = nil;
[textField resignFirstResponder];
return YES;
}
#pragma mark - Update Method
-(void)updateScrollViewPosition {
if (keyboardHeight > 0 && activeTextField) {
CGRect frame = activeTextField.frame;
CGFloat yPoint = scrollView.frame.origin.y+frame.origin.y+frame.size.height+8.0;
CGFloat height = self.view.frame.size.height-keyboardHeight;
CGFloat diff = yPoint-height;
if (diff > 0.0) {
[scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
}
else {
CGFloat diff = scrollView.contentSize.height-scrollView.contentOffset.y;
if (diff<scrollView.frame.size.height) {
diff = scrollView.contentSize.height-scrollView.frame.size.height;
if (diff < 0) {
diff = 0.0;
}
[scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
}
}
}
else {
CGFloat diff = scrollView.contentSize.height-scrollView.contentOffset.y;
if (diff<scrollView.frame.size.height) {
diff = scrollView.contentSize.height-scrollView.frame.size.height;
if (diff < 0) {
diff = 0.0;
}
[scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
}
}
}
#pragma mark
Edit: A simple resignAllTextFields method as requested. containerView is the view which contains all the UITextField.
-(void)resignAllTextFields {
for (UIView *view in containerView.subviews) {
if ([view isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField*)view;
[textField resignFirstResponder];
}
}
[self updateScrollViewPosition];
}
I try to detect when carriage goes at new line in UITextView. I can detect it by comparison total later width with UITextView width:
CGSize size = [textView.text sizeWithAttributes:textView.typingAttributes];
if(size.width > textView.bounds.size.width)
NSLog (#"New line");
But it dose not work proper way because -sizeWithAttributes:textView returns only width of letters without indentation width. Help please solve this.
This is how I would do it:
Get the UITextPosition of the last character.
Call caretRectForPosition on your UITextView.
Create a CGRect variable and initially store CGRectZero in it.
In your textViewDidChange: method, call caretRectForPosition: by passing the UITextPosition.
Compare it with the current value stored in the CGRect variable. If the new y-origin of the caretRect is greater than the last one, it means a new line has been reached.
Sample code:
CGRect previousRect = CGRectZero;
- (void)textViewDidChange:(UITextView *)textView{
UITextPosition* pos = yourTextView.endOfDocument;//explore others like beginningOfDocument if you want to customize the behaviour
CGRect currentRect = [yourTextView caretRectForPosition:pos];
if (currentRect.origin.y > previousRect.origin.y){
//new line reached, write your code
}
previousRect = currentRect;
}
Also, you should read the documentation for UITextInput protocol reference here. It is magical, I'm telling you.
Let me know if you have any other issues with this.
answer of #n00bProgrammer in Swift-4 with more precise line break detection.
#n00bProgrammer answer is perfect except one thing it reacts differently when the user starts typing in a first line, it presents that Started New Line too.
Overcoming issue, here is the refined code
var previousRect = CGRect.zero
func textViewDidChange(_ textView: UITextView) {
let pos = textView.endOfDocument
let currentRect = textView.caretRect(for: pos)
self.previousRect = self.previousRect.origin.y == 0.0 ? currentRect : self.previousRect
if currentRect.origin.y > self.previousRect.origin.y {
//new line reached, write your code
print("Started New Line")
}
self.previousRect = currentRect
}
For Swift use this
previousRect = CGRectZero
func textViewDidChange(textView: UITextView) {
var pos = textView.endOfDocument
var currentRect = textView.caretRectForPosition(pos)
if(currentRect.origin.y > previousRect?.origin.y){
//new line reached, write your code
}
previousRect = currentRect
}
You can use the UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText: (NSString *)text
{
BOOL newLine = [text isEqualToString:#"\n"];
if(newLine)
{
NSLog(#"User started a new line");
}
return YES;
}
Swift 3
The accepted answer and the swift version works fine, but here is a Swift 3 version for the lazy people out there.
class CustomViewController: UIViewController, UITextViewDelegate {
let textView = UITextView(frame: .zero)
var previousRect = CGRect.zero
override func viewDidLoad(){
textView.frame = CGRect(
x: 20,
y: 0,
width: view.frame.width,
height: 50
)
textView.delegate = self
view.addSubview(textView)
}
func textViewDidChange(_ textView: UITextView) {
let pos = textView.endOfDocument
let currentRect = textView.caretRect(for: pos)
if previousRect != CGRect.zero {
if currentRect.origin.y > previousRect.origin.y {
print("new line")
}
}
previousRect = currentRect
}
}
SWIFT 4
If you don't want to use previousRect. Let's try this:
func textViewDidChange(_ textView: UITextView) {
let pos = textView.endOfDocument
let currentRect = textView.caretRect(for: pos)
if (currentRect.origin.y == -1 || currentRect.origin.y == CGFloat.infinity){
print("Yeah!, I've gone to a new line")
//-1 for new line with a char, infinity is new line with a space
}
}
SWIFT 5
Lets not overcomplicate things.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
// return pressed
}
}
You need to get the height of the text, not the width. Use either sizeWithFont:constrainedToSize:lineBreakMode: (if you need to support iOS 6 or earlier) or use boundingRectWithSize:options:attributes:context: if you only support iOS 7.
How can we change color of UIScrollview's scroll indicator to something like blue, green etc.
I know we can change it to white, black. But other then these colors.
Many Thanks
Unfortunately you can't, of course you can always roll your own. These are your options:
UIScrollViewIndicatorStyleDefault:
The default style of scroll indicator, which is black with a white border. This style is good against any content background.
UIScrollViewIndicatorStyleBlack:
A style of indicator which is black and smaller than the default style. This style is good against a white content background.
UIScrollViewIndicatorStyleWhite:
A style of indicator is white and smaller than the default style. This style is good against a black content background.
Here's more safe Swift 3 method:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let verticalIndicator = scrollView.subviews.last as? UIImageView
verticalIndicator?.backgroundColor = UIColor.green
}
Both UIScrollView indicator are sub view of UIScrollView. So, we can
access subview of UIScrollView and change the property of subview.
1 .Add UIScrollViewDelegate
#interface ViewController : UIViewController<UIScrollViewDelegate>
#end
2. Add scrollViewDidScroll in implementation section
-(void)scrollViewDidScroll:(UIScrollView *)scrollView1
{
//get refrence of vertical indicator
UIImageView *verticalIndicator = ((UIImageView *)[scrollView.subviews objectAtIndex:(scrollView.subviews.count-1)]);
//set color to vertical indicator
[verticalIndicator setBackgroundColor:[UIColor redColor]];
//get refrence of horizontal indicator
UIImageView *horizontalIndicator = ((UIImageView *)[scrollView.subviews objectAtIndex:(scrollView.subviews.count-2)]);
//set color to horizontal indicator
[horizontalIndicator setBackgroundColor:[UIColor blueColor]];
}
Note:- Because these indicator update every time when you scroll
(means reset to default). SO, we put this code in scrollViewDidScroll
delegate method.
Demo available on GitHub - https://github.com/developerinsider/UIScrollViewIndicatorColor
Based on the answer of #Alex (https://stackoverflow.com/a/58415249/3876285), I'm posting just a little improvement to change the color of scroll indicators.
extension UIScrollView {
var scrollIndicators: (horizontal: UIView?, vertical: UIView?) {
guard self.subviews.count >= 2 else {
return (horizontal: nil, vertical: nil)
}
func viewCanBeScrollIndicator(view: UIView) -> Bool {
let viewClassName = NSStringFromClass(type(of: view))
if viewClassName == "_UIScrollViewScrollIndicator" || viewClassName == "UIImageView" {
return true
}
return false
}
let horizontalScrollViewIndicatorPosition = self.subviews.count - 2
let verticalScrollViewIndicatorPosition = self.subviews.count - 1
var horizontalScrollIndicator: UIView?
var verticalScrollIndicator: UIView?
let viewForHorizontalScrollViewIndicator = self.subviews[horizontalScrollViewIndicatorPosition]
if viewCanBeScrollIndicator(view: viewForHorizontalScrollViewIndicator) {
horizontalScrollIndicator = viewForHorizontalScrollViewIndicator.subviews[0]
}
let viewForVerticalScrollViewIndicator = self.subviews[verticalScrollViewIndicatorPosition]
if viewCanBeScrollIndicator(view: viewForVerticalScrollViewIndicator) {
verticalScrollIndicator = viewForVerticalScrollViewIndicator.subviews[0]
}
return (horizontal: horizontalScrollIndicator, vertical: verticalScrollIndicator)
}
}
If you don't add .subviews[0], you will get the deeper view and when you try to change the color of the indicator, this will appear with a weird white effect. That's because there is another view in front of it:
By adding .subviews[0] to each indicator view, once you try to change the color by calling:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
DispatchQueue.main.async() {
scrollView.scrollIndicators.vertical?.backgroundColor = UIColor.yourcolor
}
}
You will access to the first view and change the color properly:
Kudos to #Alex who posted a great solution 👍
in IOS 13
Try this one
func scrollViewDidScroll(_ scrollView: UIScrollView){
if #available(iOS 13, *) {
(scrollView.subviews[(scrollView.subviews.count - 1)].subviews[0]).backgroundColor = UIColor.themeColor(1.0) //verticalIndicator
(scrollView.subviews[(scrollView.subviews.count - 2)].subviews[0]).backgroundColor = UIColor.themeColor(1.0) //horizontalIndicator
} else {
if let verticalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as? UIImageView) {
verticalIndicator.backgroundColor = UIColor.themeColor(1.0)
}
if let horizontalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 2)] as? UIImageView) {
horizontalIndicator.backgroundColor = UIColor.themeColor(1.0)
}
}
}
Swift 2.0 :
Add UIScrollView Delegate.
func scrollViewDidScroll(scrollView: UIScrollView){
let verticalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as! UIImageView)
verticalIndicator.backgroundColor = UIColor.greenColor()
let horizontalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 2)] as! UIImageView)
horizontalIndicator.backgroundColor = UIColor.blueColor()
}
Try this it would certainly help you
for ( UIView *view in scrollBar.subviews ) {
if (view.tag == 0 && [view isKindOfClass:UIImageView.class])
{
UIImageView *imageView = (UIImageView *)view;
imageView.backgroundColor = [UIColor yellowColor];
}
}
Explanation: UIScrollBar is a collection of subviews. Here scrollBar indicator(vertical/horizontal) is the one of the subviews and it's an UIImageView.So if we set custom color to the UIImageView it effects scrollBar Indicator.
You can change an image of indicator, but you should do this repeadeatly
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.chageScrollIndicator()
}
func chageScrollIndicator (){
if let indicator = self.collection.subviews.last as? UIImageView {
let edge = UIEdgeInsets(top: 1.25,
left: 0,
bottom: 1.25,
right: 0)
indicator.image = UIImage(named: "ScrollIndicator")?.withRenderingMode(.alwaysTemplate).resizableImage(withCapInsets: edge)
indicator.tintColor = UIConfiguration.textColor
}
}
You can use this 2 image as template:
in IOS 13
Since iOS13 scroll indicators have class _UIScrollViewScrollIndicator, not UIImageView.
Many people used code like
let verticalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as! UIImageView)
It's not good idea, because they promised that last subview will be UIImageView :). Now it's not and they can get crash.
You can try following code to get scrollView indicators:
extension UIScrollView {
var scrollIndicators: (horizontal: UIView?, vertical: UIView?) {
guard self.subviews.count >= 2 else {
return (horizontal: nil, vertical: nil)
}
func viewCanBeScrollIndicator(view: UIView) -> Bool {
let viewClassName = NSStringFromClass(type(of: view))
if viewClassName == "_UIScrollViewScrollIndicator" || viewClassName == "UIImageView" {
return true
}
return false
}
let horizontalScrollViewIndicatorPosition = self.subviews.count - 2
let verticalScrollViewIndicatorPosition = self.subviews.count - 1
var horizontalScrollIndicator: UIView?
var verticalScrollIndicator: UIView?
let viewForHorizontalScrollViewIndicator = self.subviews[horizontalScrollViewIndicatorPosition]
if viewCanBeScrollIndicator(view: viewForHorizontalScrollViewIndicator) {
horizontalScrollIndicator = viewForHorizontalScrollViewIndicator
}
let viewForVerticalScrollViewIndicator = self.subviews[verticalScrollViewIndicatorPosition]
if viewCanBeScrollIndicator(view: viewForVerticalScrollViewIndicator) {
verticalScrollIndicator = viewForVerticalScrollViewIndicator
}
return (horizontal: horizontalScrollIndicator, vertical: verticalScrollIndicator)
}
}
If you need only one (h or v indicator) - it's better to cut this func and keep only one you need (to improve perfomance).
Also it would be good to call update func inside of DispatchQueue, to keep smoothness of scrolling.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
DispatchQueue.main.async {
scrollView.updateCustomScrollIndicatorView()
}
}
This is how the color of the scroll bar is changed:
//scroll view
UIScrollView *scView = [[UIScrollView alloc] init];
scView.frame = self.view.bounds; //scroll view occupies full parent views
scView.contentSize = CGSizeMake(400, 800);
scView.backgroundColor = [UIColor lightGrayColor];
scView.indicatorStyle = UIScrollViewIndicatorStyleBlack;
scView.showsHorizontalScrollIndicator = NO;
scView.showsVerticalScrollIndicator = YES;
scView.scrollEnabled = YES;
[self.view addSubview: scView];
If you wish to add image as well, here is the code for Swift 3
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let verticalIndicator = scrollView.subviews.last as? UIImageView
verticalIndicator?.image = UIImage(named: "imageName")
}
This works for UITableView and UICollectionView as well.
I wrote an article about this not so far ago. Unfortunately color of this bars defined by pre-defined images, so if you are going to change the color of bars some extra work will be required. Take a look to following link, you will definitely find an answer here since I tried to solve the same issue.
http://leonov.co/2011/04/uiscrollviews-scrollbars-customization/
I ran into the same problem recently so I decided to write a category for it.
https://github.com/stefanceriu/UIScrollView-ScrollerAdditions
[someScrollView setVerticalScrollerTintColor:someColor];
[someScrollView setHorizontalScrollerTintColor:someColor];`
It blends it with the original image so only the color will change. On the other hand, it can also be modified to provide a custom image for the scrollers to use.
Here is what I did in Swift 4, similar to previous answers. In my case I'm recoloring the image to be invisible, set correct corner radius and only execute this process once.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let color = UIColor.red
guard
let verticalIndicator = scrollView.subviews.last as? UIImageView,
verticalIndicator.backgroundColor != color,
verticalIndicator.image?.renderingMode != .alwaysTemplate
else { return }
verticalIndicator.layer.masksToBounds = true
verticalIndicator.layer.cornerRadius = verticalIndicator.frame.width / 2
verticalIndicator.backgroundColor = color
verticalIndicator.image = verticalIndicator.image?.withRenderingMode(.alwaysTemplate)
verticalIndicator.tintColor = .clear
}
please use below code on iOS Renderer
private bool _layouted;
public override void LayoutSubviews()
{
base.LayoutSubviews();
if (!_layouted)
{
this.Layer.BorderColor = UIColor.Red.CGColor;
var Verticalbar = (UIImageView)this.Subviews[this.Subviews.Length - 1];
Verticalbar.BackgroundColor = Color.FromHex("#0099ff").ToUIColor();
var Horizontlebar = (UIImageView)this.Subviews[this.Subviews.Length - 2];
Horizontlebar.BackgroundColor = Color.FromHex("#0099ff").ToUIColor();
_layouted = true;
}
}
As for iOS 13 subviews changed so adding simple if, solved this issues.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 13.0) {
UIView *verticalIndicator = [scrollView.subviews lastObject];
verticalIndicator.backgroundColor = [UIColor redColor];
} else {
UIImageView *verticalIndicator = [scrollView.subviews lastObject];
verticalIndicator.backgroundColor = [UIColor redColor];
}
}
You can use custom UIScrollView scrollBars to implement color in scrollbars. For more details look here
My goal is to mark all visible misspelled words in an UITextView.
The inefficient algorithm is to use the spell checker to find all ranges of misspelled words in the text, convert them to UITextRange objects using positionFromPosition:inDirection:offset etc, then get the graphics rects using the UITextInput method firstRectFromRange.
Thus all the text -> misspelled words-> NSRange collection -> UITextRange collection -> CGRect collection -> evaluate for visibility, draw visible ones
The problem is that this requires that all the text is checked, and all misspelled words are converted to graphics rects.
Thus, I imagine the way to go is to somehow find out what parts of the underlying .text in the UITextView that is visible at the moment.
Thus for range of text visible -> misspelled words-> NSRange collection -> UITextRange collection -> CGRect collection -> evaluate for visibility, draw visible ones
The code in ios - how to find what is the visible range of text in UITextView? might work as a way to bound what parts of the text to check, but still requires that all text is measured, which I imagine could be quite costly.
Any suggestions?
Since UITextView is a subclass of UIScrollView, its bounds property reflects the visible part of its coordinate system. So something like this should work:
- (NSRange)visibleRangeOfTextView:(UITextView *)textView {
CGRect bounds = textView.bounds;
UITextPosition *start = [textView characterRangeAtPoint:bounds.origin].start;
UITextPosition *end = [textView characterRangeAtPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))].end;
return NSMakeRange([textView offsetFromPosition:textView.beginningOfDocument toPosition:start],
[textView offsetFromPosition:start toPosition:end]);
}
This assumes a top-to-bottom, left-to-right text layout. If you want to make it work for other layout directions, you will have to work harder. :)
Rob's answer, written in Swift 4. I've added some safety checks.
private func visibleRangeOfTextView(textView: UITextView) -> NSRange {
let bounds = textView.bounds
let origin = CGPoint(x: 10, y: 10)
guard let startCharacterRange = textView.characterRange(at: origin) else {
return NSRange(location: 0, length: 0)
}
let startPosition = startCharacterRange.start
let endPoint = CGPoint(x: bounds.maxX,
y: bounds.maxY)
guard let endCharacterRange = textView.characterRange(at: endPoint) else {
return NSRange(location: 0, length: 0)
}
let endPosition = endCharacterRange.end
let startIndex = textView.offset(from: textView.beginningOfDocument, to: startPosition)
let endIndex = textView.offset(from: startPosition, to: endPosition)
return NSRange(location: startIndex, length: endIndex)
}
Example usage, called from a button tap:
#IBAction func buttonTapped(sender: AnyObject) {
let range = visibleRangeOfTextView(textView: self.textView)
// Note: "as NSString" won't work correctly with Emoji and stuff,
// see also: http://stackoverflow.com/a/24045156/1085556
let nsText = self.textView.text as NSString
let text = nsText.substring(with: range)
NSLog("range: \(range), text = \(text)")
}