I am currently doing a webview. My problem is that I want to prevent iOS from scrolling inside. The scrolling I speak is not the scrolling created in the Web view, but the bouncing scrolling that occurs when you touch up or up by touching down.
It doesn't work for me.
override func loadView() {
super.loadView()
WKWebView.scrollView.bounces = false
URLCache.shared.removeAllCachedResponses()
URLCache.shared.diskCapacity = 0
URLCache.shared.memoryCapacity = 0
...
class WebViewController: ... UIScrollViewDelegate {
override func loadView() {
super.loadView()
mainWebView.scrollView.bounces = false
mainWebView.scrollView.delegate = self
...
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height) {
scrollView.setContentOffset(CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentSize.height - scrollView.frame.size.height), animated: false)
}
}
I'm using WKWebView
Thanks you in advance.
This problem was where the function was executed. This function does not work when executed in the LoadView function. You must run it in the ViewDidLoad function.
override func viewDidLoad() {
super.viewDidLoad()
WKWebView.scrollView.bounces = false
Related
In attempt to improve the UI for smaller devices, when a UIScrollView contentSize.height is greater than it's bounds.height (I'm calling this an overflow) I want to tweak the UI.
My issue is: I'm finding the layout of the views is different after appearing on screen (in viewDidAppear(:)) than it is in the last viewDidLayoutSubviews() (could be called multiple times).
In order to determine if a scrollView is overflowing, we need to know both the scrollView.bounds and scrollView.contentSize. Which I thought would be determined in the last call of viewDidLayoutSubviews().
Below examples my scenario. It's running an interface built in xib with the scrollView set up there too. Note there are no hiding/showing of views to interfere with the layout values, simply runs through.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
printOverflow(type: #function)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
printOverflow(type: #function)
}
private func printOverflow(type: String) {
let isOverflowY = scrollView.contentSize.height > scrollView.bounds.height
debugPrint("[Overflow] \(type) \(isOverflowY ? "Overflow" : "Fits")")
}
// After running prints:
// "[Overflow] viewDidLayoutSubviews() Fits"
// "[Overflow] viewDidAppear(_:) Overflow"
My question is: why can I not get the final layout of the subviews in the last viewDidLayoutSubviews()?
I also tried adding a view.layoutIfNeeded() in viewWillAppear(:), indeed this fires another viewDidLayoutSubviews() as you'd expect but the bounds properties are still different to that in viewDidAppear(:) (despite actually now printing "Overflow")
From Apple's docs (italics are mine):
func viewDidLayoutSubviews()
When the bounds change for a view controller'€™s view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view'€™s subviews have been adjusted. Each subview is responsible for adjusting its own layout.
So, viewDidLayoutSubviews() can (and will) be called before subviews have been completely configured by auto-layout. That's also why we see viewDidLayoutSubviews() being called multiple times during loading / displaying.
If you subclass UIScrollView like this:
class MyScrollView: UIScrollView {
override func layoutSubviews() {
super.layoutSubviews()
printOverflow(type: #function)
}
private func printOverflow(type: String) {
let isOverflowY = contentSize.height > bounds.height
debugPrint("MyScrollView {Content Size: \(contentSize.height) Bounds Height: \(bounds.height)} [Overflow] \(type) \(isOverflowY ? "Overflow" : "Fits")")
}
}
You'll see that you get the correct heights.
What exactly are trying to do to "improve the UI for smaller devices"? There may be other (better?) options...
Edit
Here's a very simple example showing the sizing at each stage. The custom view changes its height anchor (if necessary) in its layoutSubviews() function:
import UIKit
class MySizingView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
if frame.height != 200.0 {
heightAnchor.constraint(equalToConstant: 200.0).isActive = true
}
print(#function, "self height:", frame.height)
}
}
class SizingTestViewController: UIViewController {
let myView: MySizingView = {
let v = MySizingView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myView)
NSLayoutConstraint.activate([
// constrain width and center X & Y
// its height will be set by its own height constraint
myView.widthAnchor.constraint(equalToConstant: 300.0),
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
myView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(#function, "myView height:", myView.frame.height)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(#function, "myView height:", myView.frame.height)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(#function, "myView height:", myView.frame.height)
}
}
Debug console output should be:
viewWillAppear(_:) myView height: 0.0
viewDidLayoutSubviews() myView height: 0.0
layoutSubviews() self height: 0.0
viewDidLayoutSubviews() myView height: 200.0
layoutSubviews() self height: 200.0
viewDidAppear(_:) myView height: 200.0
I just recently migrated my iOS UIWebview objective-c to WKWebkit swift, the problem am facing now is how to hide the scroll bar from the website I loaded. I have tried anything but not of it work, please can anyone help me out.
I have followed this step here How to hide scrollbar in WebView?, both the question and accepted answer but it didn't work for me.
Please I know this might be a duplicate question but have I have tried many post to solve this none work.
var lastOffsetY :CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
webViewSetup()
}
In webViewSetup
func webViewSetup(){
webView.scrollView.delegate = self
}
In viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let scrollView = webView.subviews[0] as? UIScrollView
webView.scrollView.contentSize = CGSize(width: webView.frame.size.width, height: webView.scrollView.contentSize.height)
scrollView?.bounces = false
scrollView?.decelerationRate = .fast
scrollView?.showsHorizontalScrollIndicator = false
webView.scrollView.showsHorizontalScrollIndicator = false
webView.scrollView.showsVerticalScrollIndicator = false
webView.scrollView.alwaysBounceHorizontal = false
webView.scrollView.bounces = false
}
In scrollViewDidScroll
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height) {
scrollView.setContentOffset(CGPoint(x:scrollView.contentOffset.x, y:scrollView.contentSize.height - scrollView.frame.size.height), animated: false)
}
}
//FIXING SCROLL VIEW
//Delegate Methods
func scrollViewWillBeginDragging(_ scrollView: UIScrollView){
lastOffsetY = scrollView.contentOffset.y
}
//FIXING SCROLL VIEW
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView){
let hide = scrollView.contentOffset.y > self.lastOffsetY
self.navigationController?.setNavigationBarHidden(hide, animated: true)
}
To turn off the web view's scrolling:
webView.scrollView.isScrollEnabled = false
If the code you provided is only for turning off the scrollview, that code can largely be culled down to something like this:
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
//configure webView
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.scrollView.isScrollEnabled = false
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "https://www.stackoverflow.com") {
webView.load(URLRequest(url: url))
}
}
}
Im working on UITableview load more data from api.
-scrollViewDidScroll method is calling when we first time load screen but calling when we scroll or at end of screen.
I need this when i load the screen first time.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"calledee %d",page);
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height))
{
//mycode
}
}
Swift 4,5
private var firstLoad = true
// to avoid scrollViewDidScroll called when first time view controller load
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
firstLoad = false
}
// MARK: - UIScrollViewDelegate
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if firstLoad { return }
...
}
}
This code doesn't seem to work. wondering what I'm doing wrong/missing. I assume that I: drag and drop the UIPinchGestureRecognizer to the viewcontroller (so that it doesn't interfere with the connections to buttons, label etc. and it is connected to the viewcontroller); then implement this code (make sure connections are proper):
import UIKit
class AlertController: UIAlertController {
#IBAction func scaleImage(sender: UIPinchGestureRecognizer) {
self.view.transform = CGAffineTransformScale(self.view.transform, sender.scale, sender.scale)
sender.scale = 1
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = UIColor.blackColor()
}
override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning()
}
}
Please help !
You can put your zoomeable UIView inside an UIScrollView an then implement this method
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.view
}
you need also setup your scrollView delegate and set your self.scrollView.maximumZoomScale > self.scrollView.minimumZoomScale something like this
self.scrollView.delegate = self
self.scrollView.maximumZoomScale = 2.0
self.scrollView.minimumZoomScale = 0.5
then using pitch gesture must scale your `self.view
I hope this helps you, best regards
I am using a UIPageViewController, and I need to get the scroll position of the ViewController as the users swipe so I can partially fade some assets while the view is transitioning to the next UIViewController.
The delegate and datasource methods of UIPageViewController don't seem to provide any access to this, and internally I'm assuming that the UIPageViewController must be using a scroll view somewhere, but it doesn't seem to directly subclass it so I'm not able to call
func scrollViewDidScroll(scrollView: UIScrollView) {
}
I've seen some other posts suggestion to grab a reference to the pageViewController!.view.subviews and then the first index is a scrollView, but this seems very hacky. I'm wondering if there is a more standard way to handle this.
You can search for the UIScrollView inside your UIPageViewController. To do that, you will have to implement the UIScrollViewDelegate.
After that you can get your scrollView:
for v in pageViewController.view.subviews{
if v.isKindOfClass(UIScrollView){
(v as UIScrollView).delegate = self
}
}
After that, you are able to use all the UIScrollViewDelegate-methods and so you can override the scrollViewDidScroll method where you can get the scrollPosition:
func scrollViewDidScroll(scrollView: UIScrollView) {
//your Code
}
Or if you want a one-liner:
let scrollView = view.subviews.filter { $0 is UIScrollView }.first as! UIScrollView
scrollView.delegate = self
UIPageViewController scroll doesn't work like normal scrollview and you can't get scrollView.contentOffset like other scrollViews.
so here is a trick to get what's going on when user scrolls :
first you have to find scrollview and set delegate to current viewController like other answers said.
class YourViewController : UIPageViewController {
var startOffset = CGFloat(0) //define this
override func viewDidLoad() {
super.viewDidLoad()
//from other answers
for v in view.subviews{
if v is UIScrollView {
(v as! UIScrollView).delegate = self
}
}
}
.
.
.
}
extension YourViewController : UIScrollViewDelegate{
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
startOffset = scrollView.contentOffset.x
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
var direction = 0 //scroll stopped
if startOffset < scrollView.contentOffset.x {
direction = 1 //going right
}else if startOffset > scrollView.contentOffset.x {
direction = -1 //going left
}
let positionFromStartOfCurrentPage = abs(startOffset - scrollView.contentOffset.x)
let percent = positionFromStartOfCurrentPage / self.view.frame.width
//you can decide what to do with scroll
}
}
Similar to Christian's answer but a bit more Swift-like (and not unnecessarily continuing to loop through view.subviews):
for view in self.view.subviews {
if let view = view as? UIScrollView {
view.delegate = self
break
}
}
As of iOS 13, the UIPageViewController seems to reset the scrollview's contentOffset once it transitions to another view controller. Here is a working solution:
Find the child scrollView and set its delegate to self, as other answers suggested
Keep track of the current page index of the pageViewController:
var currentPageIndex = 0
// The pageViewController's viewControllers
let orderredViewControllers: [UIViewController] = [controller1, controller2, ...]
pageViewController.delegate = self
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard completed, let currentViewController = pageViewController.viewControllers?.first else { return }
currentPageIndex = orderredViewControllers.firstIndex(of: currentViewController)!
}
Get the progress that ranges from 0 to 1
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffsetX = scrollView.contentOffset.x
let width = scrollView.frame.size.width
let offset = CGFloat(currentPageIndex) / CGFloat(orderredViewControllers.count - 1)
let progress = (contentOffsetX - width) / width + offset
}
var pageViewController: PageViewController? {
didSet {
pageViewController?.dataSource = self
pageViewController?.delegate = self
scrollView?.delegate = self
}
}
lazy var scrollView: UIScrollView? = {
for subview in pageViewController?.view?.subviews ?? [] {
if let scrollView = subview as? UIScrollView {
return scrollView
}
}
return nil
}()
extension BaseFeedViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.x
let bounds = scrollView.bounds.width
let page = CGFloat(self.currentPage)
let count = CGFloat(viewControllers.count)
let percentage = (offset - bounds + page * bounds) / (count * bounds - bounds)
print(abs(percentage))
}
}
To make the code as readable and separated as possible, I would define an extension on UIPageViewController:
extension UIPageViewController {
var scrollView: UIScrollView? {
view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView
}
}
It's quite easy to set yourself as the delegate for scroll view events, as so:
pageViewController.scrollView?.delegate = self