I don't know why but delegates have always hurt my brain.
I have the delegate like so:
protocol StylistControllerDelegate {
func userInteractsWithTableView(tableViewOffset: CGFloat)
}
It sits inside StylistController and is called when the user scrolls:
class StylistController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIScrollViewDelegate {
var delegate : StylistControllerDelegate?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
self.delegate?.userInteractsWithTableView(tableViewOffset: offset)
}
inside the cell I am attempting to set the CGFloat value to a variable:
class CollectionViewCell: UICollectionViewCell, StylistControllerDelegate {
//var stylistControllerTableViewOffset: CGFloat?
func userInteractsWithTableView(tableViewOffset: CGFloat) {
print(tableViewOffset)
if tableViewOffset > 100 {
collectionView.isScrollEnabled = true
} else {
collectionView.isScrollEnabled = false
}
}
}
I use the offset to enable and disable to ability to scroll of the collectionview INSIDE the cell. Once it gets to a certain value (in this case 100), the ability to scroll is enabled.
Related
I tried to use a UIRefreshControll in a UIScrollView but I had the problem that I needed to scroll a lot in order to get it to refresh. I needed to use both hands to make it work so I decided to write some scrolling logic to start the refreshing.
Now the problem is that sometimes the endRefreshing function doesn't make the UIRefreshControll view disappear and I end up having it there forever.
I have tried called endRefreshing in the main queue with a delay and it doesn't work. I have also tried setting isHidden to true and also removeFromSuperView. I've had no luck in making it work.
import UIKit
class RefreshableView : UIView {
#IBOutlet weak var scrollView: UIScrollView!
private let refreshControl = UIRefreshControl()
private var canRefresh = true
var refreshFunction: (() -> ())?
func setupUI() {
backgroundColor = ColorName.grayColor.color
scrollView.refreshControl = refreshControl
}
func endRefreshing() {
self.refreshControl.endRefreshing()
}
}
extension RefreshableView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < -100 {
if canRefresh && !self.refreshControl.isRefreshing {
self.canRefresh = false
self.refreshControl.beginRefreshing()
self.refreshFunction?()
}
} else if scrollView.contentOffset.y >= 0 {
self.canRefresh = true
}
}
}
The scroll view's refreshControl handles the pull-to-refresh feature automatically, so you don't need any of the scrollViewDidScroll() code.
Assuming your storyboard looks something like this, where:
the view with the Red background is an instance of RefreshableView class
it contains a scroll view (connected via #IBOutlet)
and some content in the scroll view (here I have just a single label)
Your code can be like this:
class RefreshableView : UIView {
#IBOutlet var scrollView: UIScrollView!
private let refreshControl = UIRefreshControl()
var refreshFunction: (() -> ())?
func setupUI() {
backgroundColor = .gray
refreshControl.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged)
scrollView.refreshControl = refreshControl
}
#objc func didPullToRefresh() {
print("calling refreshFunction in controller")
self.refreshFunction?()
}
func endRefreshing() {
print("end refreshing called from controller")
self.refreshControl.endRefreshing()
}
}
class RefreshTestViewController: UIViewController {
#IBOutlet var theRefreshableView: RefreshableView!
override func viewDidLoad() {
super.viewDidLoad()
theRefreshableView.setupUI()
theRefreshableView.refreshFunction = {
print("simulating refresh for 2 seconds...")
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
[weak self] in
guard let self = self else { return }
// do what you want to update the contents of theRefreshableView
// ...
// then tell theRefreshableView to endRefreshing
self.theRefreshableView.endRefreshing()
}
}
}
}
I have a UIViewController with a UIScrollView I've added in Interface Builder. I want to have scrollView functions so I can detect if it's been scrolled up or down. My scroll view is attached with an IBOutlet called "mainScroll." Here is my code:
var lastContentOffset: CGFloat = 0
#IBOutlet weak var mainScroll: UIScrollView!
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.lastContentOffset = mainScroll.contentOffset.y
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (self.lastContentOffset < mainScroll.contentOffset.y) {
// did move up
print("Did Move Up")
} else if (self.lastContentOffset > mainScroll.contentOffset.y) {
// did move down
print("Did Move Down")
} else {
// didn't move
}
}
I've added UIScrollViewDelegate to my class. I have a feeling I'm not using the scrollView delegate callbacks properly. How can I take these functions and assign them to my "mainScroll"?
May be you need this in viewDidLoad
mainScroll.delegate = self
I have a UIView subclass (a graph) embedded inside of a UIScrollview inside of a UIViewController which is one of a UIPageViewControllers pages... I'm trying to disable scrolling of the UIPageViewController so that the user scan scroll to see the far left of the graph without paging back. How can I do this? Is a ScrollView even the correct tool for this job?
class HistoricalHealthDataViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var graphScrollView: UIScrollView!
#IBOutlet weak var hockeyTrackerGraphView: HockeyTrackerGraphView! {
didSet {
self.hockeyTrackerGraphView.graphableObjects = HFRGraphableObjects
}
}
var HFRGraphableObjects: [HockeyTrackerGraphableObject] = []
override func viewDidLoad() {
super.viewDidLoad()
graphScrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let parentPageViewController = parent as? HistoricalPageViewController {
for gestureRecognizer in parentPageViewController.gestureRecognizers {
print("gestureRecognizer")
gestureRecognizer.isEnabled = false
}
}
}
}
You don't show the code for it, but I'm assuming you implement UIPageViewControllerDataSource protocol and somewhere you have the methods:
func pageViewController(UIPageViewController, viewControllerBefore: UIViewController) -> UIViewController? {
}
func pageViewController(UIPageViewController, viewControllerAfter: UIViewController) -> UIViewController? {
}
If you arrange for those methods to return nil while paging is disabled, the page view won't scroll to a different page.
I have a UIWebView nested within a UITableViewCell.
How can I adjust the cell's height depending on the content I load into the WebView (i.e. after it finishes downloading) ... ?
I have the tableview to set the heightForRowAtPath to be UITableViewAutomaticDimension.
loadContentIntoWebview is called by the owner of the TableView in it's cellForRowAt delegate methods.
I'm noticing heightForRowAt being called before cellForRowAt so my "solution" of setting the height based on the cell's webview's content size won't work.
import UIKit
class MyCell: UITableViewCell, UIWebViewDelegate {
// MARK: - instance variables
#IBOutlet weak var myWebview: UIWebView?
// MARK: - lifecycle methods
override func awakeFromNib() {
super.awakeFromNib()
myWebview?.scrollView.bounces = false;
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
// MARK: - private methods
func loadContentIntoWebview(htmlString: String) {
myWebview?.loadHTMLString(htmlString, baseURL: nil)
}
// MARK: - UIWebViewDelegate methods
func webViewDidFinishLoad(_ webView: UIWebView) {
// dynamically set WebView height
let tempString = myWebview?.stringByEvaluatingJavaScript(from: "document.body.scrollHeight")
var tempHeight: CGFloat = 0.0
if let n = NumberFormatter().number(from: tempString!) {
tempHeight = CGFloat(n)
}
myWebview?.frame.size.height = tempHeight
}
}
func webViewDidFinishLoad(_ webView: UIWebView) {
if let cell :yourCell = self.yourTblVw.cellForRow(at: yourindexpath) as? yourCell{
// set height of webview as per its content
var frame = cell.webView.frame
frame.size.height = 1
let fittingSize = cell.webView.sizeThatFits(CGSize.zero)
frame.size = fittingSize
cell.heightWebVwMain.constant = fittingSize.height
}
where heighWebVwMain is constraint of your cell's webview's height. So you have to set this constant in cellforRow method also
Found a solution ...
In webViewDidFinishLoad(_ webView: UIWebView) set the constraint's
height as in initial question but use document.body.clientHeight
Blast a notification to tell the associated tableview to not reloadData(), but rather beginUpdates() then endUpdates()
#objc func handleWebviewFinishedLoading() {
self.webview?.beginUpdates()
self.webview?.endUpdates()
}
This seems to redraw the cells without reloading them :)
In a UIViewController, I instantiate a WKWebView and set the view controller as the WKWebiew scrollview's delegate:
class MyViewController: UIViewController, UIScrollViewDelegate {
var webView : WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: self.view.frame)
view = webView
webView.loadHTMLString("<h1>hello</h1>", baseURL: NSURL(string: "http://www.google.com")!)
webView.scrollView.delegate = self
}
func scrollViewDidScroll(scrollView: UIScrollView) {
println("didScroll")
}
The problem I have is that scrollViewDidScroll is called as soon as the WKWebView is created (ie without the user actually scrolling anything).
Why is that and how can I prevent it ?
Set a bool value to true in scrollViewWillBeginDragging method, which is called only when user scrolls and it is not automatically called by WKWebview
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
scrollByDragging = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !scrollByDragging{
UIView.animate(withDuration: 0.01) {
scrollView.setContentOffset(.zero, animated: true)
}
}
}
If you were to use contentOffset, you would be able to detect when the user scrolls.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y >= 0 {
print("didScroll")
}
}
If your UIScrollView is inside a UINavigationController, contentOffset.y will start at -44, but practically 0 is fine to use.