I have two UIScrollViews in my UIViewController both of them have different properties such as pagingEnabled, contentSize & contentInset also i need them to behave separately when scrolling so i create two separate custom classes like below.
class NavigationScrollView: UIScrollView{
}
class ContentScrollView: UIScrollView{
}
also delegates
extension NavigationScrollView: UIScrollViewDelegate{
// this method not triggers
func scrollViewDidScroll(scrollView: UIScrollView) {
}
}
extension ContentScrollView: UIScrollViewDelegate{
// this method not triggers
func scrollViewDidScroll(scrollView: UIScrollView) {
}
}
In main viewController viewDidLoad() i do
navigationScroller = NavigationScrollView(frame: CGRectMake(0.0, 0.0, frameWidth, 40.0))
contentScroller = ContentScrollView(frame: CGRectMake(0.0, 0.0, frameWidth, frameHeight))
how about you assign the delegate this way?
class NavigationScrollView: UIScrollView{
override func didMoveToSuperview() {
super.didMoveToSuperview()
self.delegate = self
}
}
class ContentScrollView: UIScrollView{
override func didMoveToSuperview() {
super.didMoveToSuperview()
self.delegate = self
}
}
Related
Situation:
I am creating an imageGallery which is a viewController that I am presenting modally. I have created it all programatically, I haven't touched the storyboard.
The viewController has a scrollView that contains ImageViews, which each hold an image. I have set scrollView.isPagingEnabled = true and it works as expected, I can scroll through the images fine.
Issue:
I am trying to do some work when the scrollView has scrolled. I have set the delegate (which seems to be the main issue people have had when I searched for an answer) but scrollViewDidScroll never gets called.
I'm completely at a loss for what is happening.I've tried quite a lot of different things, but have now run out of ideas
#objc public class ImageGallery: UIViewController, UIScrollViewDelegate {
// MARK:- VARIABLES
/// An array of imageItems
#objc public var galleryItems = [ImageGalleryItem]()
/// The index of the image the gallery should start at
#objc public var startingIndex: NSNumber?
/// The UIImage to display if there is an error
#objc public var errorImage: UIImage?
/// The UIImage to display if the ImageGalleryItem doesn't have an image
#objc public var defaultImage: UIImage?
var scrollView: UIScrollView!
// MARK:- INITS
/// Initialiser for ImageGallery passing an array of ImageGalleryItems
///
/// - Parameter ImageGalleryItems: an array of ImageGalleryItems
#objc public init() {
self.defaultImage = UIImage(named: "default", in: getResourceBundle(), compatibleWith: nil)!
self.errorImage = UIImage(named: "error", in: getResourceBundle(), compatibleWith: nil)!
super.init(nibName:nil, bundle:nil)
}
#objc public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
}
// MARK:- VIEW LIFECYCLES
override public func viewDidLoad() {
super.viewDidLoad()
configureScrollView()
self.scrollView.delegate = self
configureImageViews()
configurePresentationDefaults()
}
func configureScrollView() {
self.scrollView = UIScrollView(frame: view.frame)
self.scrollView.isPagingEnabled = true
self.scrollView.contentOffset = CGPoint(x: CGFloat(truncating: startingIndex ?? 0) * UIScreen.main.bounds.width, y: 0)
let imagesCount = galleryItems.count
let contentWidth = CGFloat(imagesCount) * UIScreen.main.bounds.width
self.scrollView.contentSize = CGSize(width: contentWidth, height: UIScreen.main.bounds.height)
if let toolbar = configureToolBar() {
view.addSubview(self.scrollView)
view.addSubview(toolbar)
view.bringSubviewToFront(toolbar)
}
}
private func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("delegate")
}
Update
I was missing an !
private func scrollViewDidScroll(_ scrollView: UIScrollView!) {
print("delegate")
}
Did the trick.
Remove private keyword
func scrollViewDidScroll(_ scrollView: UIScrollView)
You issue comes from the private access level keyword with the scrollViewDidScroll: delegate method. The UIScrollViewDelegate is an NSObject and as the method is private it can't see the method and so the delegate method is not called. As you class is public, you need to declare the method with a public access level.
Just try this:
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("delegate")
}
I was missing an !
private func scrollViewDidScroll(_ scrollView: UIScrollView!) {
print("delegate")
}
Solved the issue.
I have a class called BaseViewController which contains a function where I can add a header to my VC and anchor it
class BaseViewController: UIViewController {
let headerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.green
return view
}()
func addHeader() {
view.addSubview(headerView)
// then anchor it to top
}
}
I have another class called ScrollViewController which adds a scrollViewController to my VC and anchors it
class ScrollViewController: UIViewController {
let scrollView: UIScrollView = {
let view = UIScrollView()
view.backgroundColor = UIColor.green
return view
}()
func addScrollView() {
view.addSubview(scrollView)
// then anchor it to top
}
}
Finally my main class HomeViewController wants to have both a header and a scrollView so I inherit from both like this:
class HomeViewController: ScrollViewController, BaseViewController {
override viewDidLoad() {
super.viewDidLoad()
addScrollView()
addHeaderView()
let view = UIView()
//anchor view to bottom of the header
}
}
However swift doesn't allow multiple inheritance so I tried using protocols and extensions which works but the problem is that I want other views to be able to be anchored to the header and scrollView so it didn't fit my needs.
What could I do so I can implement something like that
Thanks
Swift does not support multiple inheritance. However, protocols and protocol extensions can accomplish what you want.
Example:
protocol HeaderProtocol {
func addHeaderView() -> UIView
}
extension HeaderProtocol where Self: UIViewController {
func addHeaderView() -> UIView {
let headerView = UIView()
headerView.backgroundColor = UIColor.green
view.addSubview(headerView)
// then anchor it to top
return headerView
}
}
protocol ScrollViewProtocol {
func addScrollView() -> UIView
}
extension ScrollViewProtocol where Self: UIViewController {
func addScrollView() -> UIView {
let scrollView = UIScrollView()
scrollView.backgroundColor = UIColor.green
view.addSubview(scrollView)
// then anchor it to top
return scrollView
}
}
class HomeViewController: UIViewController, ScrollViewProtocol, HeaderProtocol {
override func viewDidLoad() {
super.viewDidLoad()
let scrollView = addScrollView()
let headerView = addHeaderView()
}
}
Alternative approach:
protocol HeaderProtocol {
var headerView: UIView? { get set }
func addHeaderView() -> UIView
}
extension HeaderProtocol where Self: UIViewController {
func addHeaderView() -> UIView {
let headerView = UIView()
headerView.backgroundColor = UIColor.green
view.addSubview(headerView)
// then anchor it to top
return headerView
}
}
protocol ScrollViewProtocol {
var scrollView: UIView? { get set }
func addScrollView() -> UIView
}
extension ScrollViewProtocol where Self: UIViewController {
func addScrollView() -> UIView {
let scrollView = UIScrollView()
scrollView.backgroundColor = UIColor.green
view.addSubview(scrollView)
// then anchor it to top
return scrollView
}
}
class HomeViewController: UIViewController, ScrollViewProtocol, HeaderProtocol {
var scrollView: UIView?
var headerView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
scrollView = addScrollView()
headerView = addHeaderView()
}
}
This is driving me nuts!
I'm basing my UIScrollView on http://koreyhinton.com/blog/uiscrollview-crud.html to make it programatic, so have set up a container view inside my scrollview. But it pans, but won't zoom.
class BinaryTreeViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var containerView : UIView!
override func viewDidLoad() {
super.viewDidLoad()
let width:CGFloat = self.view.bounds.width
let height:CGFloat = self.view.bounds.height
scrollView = UIScrollView()
scrollView.delegate = self
scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 2.0
scrollView.contentSize = CGSize(width: width*2, height: 2000)
scrollView.backgroundColor = .red
containerView = UIView()
scrollView.addSubview(containerView)
view.addSubview(scrollView)
containerView.isUserInteractionEnabled = true
scrollView.isUserInteractionEnabled = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
containerView.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
}
override func viewWillLayoutSubviews() {
//I create a view called "theView"
containerView.addSubview(theView)
}
The following functions do not fire at any point
func update(zoomScale: CGFloat, offSet: CGPoint) {
scrollView.zoomScale = zoomScale
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return containerView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
NSLog("scroll")
}
You really don't need to do so much code for that purpose.
You can set up all you need for scrollView in storyboard, and you only need outlet for the view you wish to zoom.
Set up a controller, add scrollview, connect delegate property to view controller, add zooming view as subview in IB.
In the class, conform controller to UIScrollViewDelegate, and use viewForZooming, a scrollView delegate method.
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var zoomer: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return zoomer
}
}
P.S. Use newer resources for learning, Ray Wenderlich, AppCoda, etc - its a big web full of good sources, and Swift is in constant change.
When I instantiate and add a scroll view to my self.view in my ViewController class and set the scroll view's delegate to self, the delegate functions get called. As written below:
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var smallView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let width = Double(view.frame.width)
let height = Double(view.frame.height)
let frame = CGRect(x: 0.0, y: 0.0, width: width, height: height)
let scrollView = UIScrollView(frame: frame)
//scrollView.backgroundColor = UIColor.blue
let size = CGSize(width: width + 300, height:1000)
scrollView.contentSize = size
smallView.addSubview(scrollView)
scrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("Gets called.")
}
}
However, when I create a custom class, in this case called, PhotoBooth, and call try to call the delegate function in this custom class, the functions do not get called. Here is my custom class:
import Foundation
import UIKit
class PhotoBooth: NSObject, UIScrollViewDelegate {
private var boothView: UIView
//private var scrollView: UIScrollView
init(view: UIView) {
boothView = view
}
func startSession() {
let width = Double(boothView.frame.width)
let height = Double(boothView.frame.height)
let frame = CGRect(x: 0.0, y: 0.0, width: width, height: height)
let scrollView = UIScrollView(frame: frame)
//scrollView.backgroundColor = UIColor.blue
let size = CGSize(width: width + 300, height:1000)
scrollView.contentSize = size
boothView.addSubview(scrollView)
scrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("paisdjfij")
}
}
And I instantiate the class in my ViewController like so:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var smallView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let photoBooth = PhotoBooth(view: self.view)
photoBooth.startSession()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Any solutions to the problem? Please let me know and thank you so much for your help in advance.
Instead of trying this You should use protocols to call scroll view in custom class.It will look somewhat like this.
CustomClass.h
#protocol CustomDelegate <NSObject>
-(void)customDelegateMethod;
#end
#interface CustomClass : UIScrollView <UIScrollViewDelegate>
{
id<CustomDelegate> delegate
}
CustomClass.m
-(void) methodScrollView
{
[self.delegate customDelegateMethod];
}
ViewController.h
#interface ViewController: UIViewController <CustomDelegate>
{
}
ViewController.m
-(void)makeCustomScrollView
{
CustomClass *objCustom = [[CustomClass alloc] init];
objCustom.delegate = self;
//other stuff
}
-(void)customDelegateMethod
{
}
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.