I have a TextView and a hidden button within a UIView, and I'm trying to detect when the user scrolls down to the bottom of a long list of text and to show the hidden button when they reach the bottom. I saw some old posts on how it was done in Obj-C using scrollViewDidScroll, but not really sure how to do that with swift, or how to do it with a TextView instead of a ScrollView. Any help would be great as I haven't gone very far with this one.
So far this is my attempt at translating the obj-c post to swift, but it hasn't worked for me, in fact I'm not even sure when the function is called:
import UIKit
class MainVC: UIViewController, UIScrollViewDelegate {
#IBOutlet var textView: UIScrollView!
#IBOutlet var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
}
func scrollViewDidScroll(textV: UIScrollView) {
if (textV.contentOffset.y >= textV.contentSize.height - textV.frame.size.height)
{
button.isHidden = false
}
}
}
Thanks for any help in advance :)
UITextView is subclass of UIScrollView and if you look to declaration, you will see, that it is UIScrollViewDelegate by default, so you can remove the UIScrollViewDelegate at the declaration of your controller. Instead, make your controller UITextViewDelegate which allows it to call scrollViewDidScrollMethod.
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView! {
didSet {
textView.delegate = self
}
}
#IBOutlet weak var button: UIButton! {
didSet {
button.hidden = true
}
}
func scrollViewDidScroll(scrollView: UIScrollView) {
button.hidden = scrollView.contentOffset.y + scrollView.bounds.height < scrollView.contentSize.height
}
}
(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height
if (bottomEdge >= scrollView.contentSize.height) {
self.yourButtonName.hidden = true
}
}
If the button is in the last of view(self.view) then I think you have to check that your contentOffset point is at the bottom of contentSize. So you could probably do something like:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
float bottomEdge = scrollView.contentOffset.y +scrollView.frame.size.height;
if (bottomEdge >= scrollView.contentSize.height) {
self.yourButtonName.hidden = true
}
}
Related
I have the following code:
extension SegmentedControlViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// this checks for whether contentOffset.x is around 10 (contented shifted to the right by 10 points)
if abs(scrollView.contentOffset.x - 10) < 1 {
print("works")
}
}
}
#IBOutlet weak var scrollView: UIScrollView!
then I call the function in the viewDidLoad like this:
scrollViewDidScroll(scrollView)
Although I am always getting the error message
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
What am I doing wrong, how to Get the scroll view to not be nil.
Assign the scroll view's .delegate in viewDidLoad() and start scrolling:
class SegmentedControlViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// assign the delegate
scrollView.delegate = self
}
}
extension SegmentedControlViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// this checks for whether contentOffset.x is around 10 (contented shifted to the right by 10 points)
if abs(scrollView.contentOffset.x - 10) < 1 {
print("works")
}
}
}
Edit
If you want to check if the scroll view content has been dragged to the right more than 10 points, use this:
extension SegmentedControlViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// this checks for whether contentOffset.x is around 10 (contented shifted to the right by 10 points)
if scrollView.contentOffset.x < 10 {
print("scroll content has been dragged to the right more than 10 points")
}
}
}
I followed this tutorial to create a Snapchat-Like Menu: https://www.youtube.com/watch?v=1_daE3IL_1s
In short, the idea was to create custom views with XIB, and add them into a scrollview.
I would like to add a "back" button to one of the custom views so that the scrollview will automatically scroll back to its initial view when the button is tapped. The Idea I had was to write a code inside the custom view's IBAction function to call UIScrollview's content offset delegate function.
the following is the code in View Controller:
class ViewController: UIViewController, UIScrollViewDelegate {
//I'm naming the Scroll View as "scrollView"
#IBOutlet weak var scrollView: UIScrollView! {
didSet{
scrollView.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var v1 : View1 = View1(nibName: "View1", bundle: nil)
var v2 : View2 = View2(nibName: "View2", bundle: nil)
self.addChild(v1)
self.scrollView.addSubview(v1.view)
v1.didMove(toParent: self)
self.addChild(v2)
self.scrollView.addSubview(v2.view)
v1.didMove(toParent: self)
var v2Frame : CGRect = v2.view.frame
v2Frame.origin.x = self.view.frame.width
v2.view.frame = v2Frame
self.scrollView.contentSize = CGSize(width: self.view.frame.width * 2, height: self.view.frame.height)
// self.scrollView.contentSize = CGSize(self.view.frame.width * 2, self.view.frame.size.height)
}
}
The following ht the code in View2:
class View2: UIViewController {
#IBOutlet weak var buttonHome: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonHomeAction(_ sender: Any) {
//how to access scrollview.setContentOffset from ViewController to scroll the view back to the first view?
}
So my question is how to access scrollview.setContentOffset from ViewController to scroll the view back to the first view? Am I on the right track? Or are there any alternative methods where I can tap a button and scroll the view back to its first page?
Thank you!
I can think of two good options here:
1) Hold a weak reference in View2 of the scrollView. in View2 add:
weak var scrollView: UIScrollView?
and in ViewController, after view2 creation add:
view2.scrollView = self.scrollView
now, in buttonHomeAction you'll have access to the scrollView.
2) If you are familiar with Apple's delegation pattern, you can add a delegate, which can be a weak reference of the presenting view controller.
Create a protocol for the delegate, using named by the view holds the delegate. so, in this case: View2Delegate
and add a method that describes the action.
protocol View2Delegate: AnyObject {
func buttonTapped()
}
so, in View2 add:
weak var delegate: View2Delegate?
and in ViewController, after view2 creation add:
view2.delegate = self
now, in ViewController implement the protocol method and access scrollView:
extension ViewController: View2Delegate {
func buttonTapped() {
// access scrollView as self.scrollView
}
}
I'm trying to add a shadow to my (custom) nav bar when a user scrolls using scrollViewDidScroll but it doesn't do anything. I have the same exact code on another view controller but it has a tableView instead of a WKWebView and it works fine.
I tried adding webView.scrollView.delegate = self but I just get an error.
My code:
class ViewController: UIViewController {
#IBOutlet weak var webView: WKWebView!
#IBOutlet weak var navBar: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addShadow()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let navigationBar = navBar
let offset = scrollView.contentOffset.y / 10
if offset > 1.5 {
navigationBar?.layer.shadowOpacity = 0.15
} else {
navigationBar?.layer.shadowOpacity = Float(((3 * offset) / 20)/1.5)
}
}
func addShadow() {
navBar.layer.shadowColor = UIColor.black.cgColor
navBar.layer.shadowOffset = CGSize(width: 0, height: 2.0)
navBar.layer.shadowRadius = 6.0
navBar.layer.masksToBounds = false
}
}
I tried adding webView.scrollView.delegate = self but I just get an error.
You didn't share what the error was. I suspect though that you didn't declare your view controller as UIScrollViewDelegate conforming, which caused the compile time error when trying to set self as the scroll view delegate.
Change your view controller declaration to this:
class ViewController: UIViewController, UIScrollViewDelegate {
I have a UITableView, where each row is a custom XIB. One row has 4 UIButtons, that are choices, like Question, Information, etc. When the UIButton is tapped I display an animation in a CALayer. When the table view scrolls, the CALayer is removed, that is the animation is gone.
When the button is tapped, I crate the CALayer and the animation begins. How can I make sure the CALayer doesn't disappear when the table is scrolled or updated?
class ReasonForSupportTableViewCell: UITableViewCell {
let animationView = AnimationView()
var hasButtonBeenTapped = false
var previousButtonTapped: UIButton? = nil
//IBOutlets
#IBOutlet weak var questionButton: UIButton!
#IBOutlet weak var informationButton: UIButton!
#IBOutlet weak var crashButton: UIButton!
#IBOutlet weak var bugButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
//set initial button
questionButton.sendActions(for: .touchUpInside)
}
func createView(sender: UIButton) {
//draw the frame
animationView.frame = CGRect(x: 0, y: 0, width: 75.0, height: 75.0)
//change the view background color
animationView.backgroundColor = UIColor.clear
animationView.isUserInteractionEnabled = false
//add the view.
sender.addSubview(animationView)
}
#IBAction func buttonTapped(_ sender: UIButton) {
if previousButtonTapped == nil {
//you haven't tapped a button yet
createView(sender: sender)
animationView.animateTo()
previousButtonTapped = sender
} else if previousButtonTapped == sender {
animationView.removeAnimation()
previousButtonTapped = nil
} else {
animationView.removeAnimation()
createView(sender: sender)
animationView.animateTo()
previousButtonTapped = sender
}
}
}
Subclass UIButton to add the CALayer on initialisation, rather than when a button is tapped. You can then use the prepareForReuse() function to stop any animation.
I am trying to make an app that utilizes some search feature and I am trying to make it so that after the search button is pressed, a view (which contains the search results) moves up from the bottom of the superview and replaces the search view. On the storyboard, the resultsView (of type UIView) is constrained so that its top is equal to the superview's bottom. After the search button is pressed, I would like to animate the view to move up and replace the view already at the bottom of the superview. The problem is, in the viewcontroller's class, when I call the resultsView, the animateWithDuration(NSTimeInterval) that is supposed to be associated with the UIView class is not appearing for me. May this be because the view is already constrained in place? Here is the code, simplified for this post:
import UIKit
import MapKit
class MapViewController: UIViewController, CLLocationManagerDelegate,
MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var slider: UISlider!
#IBOutlet weak var distanceLabel: UILabel!
#IBOutlet weak var searchButton: UIButton!
#IBOutlet weak var searchView: UIView!
#IBOutlet weak var resultView: UIView!
#IBOutlet weak var resultNameLabel: UILabel!
#IBOutlet weak var resultDistanceLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
self.resultView.isHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func sliderAdjusted(_ sender: Any) {
let int = Int(slider.value)
switch int {
case 1:
distanceLabel.text = "1 mile"
default:
distanceLabel.text = "\(int) miles"
}
}
#IBAction func searchButtonPressed(_ sender: Any) {
/*This is where, after calling a search function which is omitted
from this example, I would like to make the resultsView not
hidden and then animate it to sort of eclipse the search view*/
self.resultView.isHidden = false
self.resultView.animate(withDuration: NSTimeInterval)
/*The above line of code is not actually appearing for me.
After typing "self.resultView." the animate function is not being
given to me as an option for my UIView*/
}
}
I will also attach some images of the view controller so you can sort of get the idea. The results view is not visible in this image because its top is constrained to the superview's bottom, thus it is just out of the visible representation of its superview.
The first image is the view controller with the searchView highlighted. This is the view that I would like to be eclipsed by my resultView after the searchButton is pressed.
The second image is the same view controller with the resultView highlighted. As you can see, its top is constrained to be equal to the superview's bottom. This is the view that I would like to animate upwards into the superview and eclipse the searchView after the search button is pressed.
The methods for all the animate family are all class methods. Which means you call them on the class object not an instance.
You are trying to call
class func animate(withDuration: TimeInterval, animations: () -> Void)
so your code needs to look like
UIView.animate(withDuration: 0.5) {
//the things you want to animate
//will animate with 0.5 seconds duration
}
In the particular case it looks like you are trying to animate the height of resultView so you need an IBOutlet to that constraint. You could call it resultViewHeight.
UIView.animate(withDuration: 0.5) {
self.resultViewHeight.constant = theDesiredHeight
self.layoutIfNeeded()
}
Calling layoutIfNeeded() within the closure is the secret sauce to animating auto layout. Without that the animation will just jump to the and point.