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()
}
}
Related
When I try to compile this code I get compile time error that's saying:
Protocol 'KeyboardScrollManagable' can only be used as a generic constraint because it has Self or associated type requirements
If I remove lines with //One and //Two comments it will work, but then AbcView doesn't have to be Scrollable anymore. I want to enforce that if you use KeyboardScrollManagable on any a ViewController's subclass, it's View will need to be Scrollable!
protocol KeyboardScrollManagable: UIViewController {
associatedtype View where View: Scrollable //One
var customView: View { get } //Two
func doSomething()
}
class ViewController<ViewModel, View: UIView>: UIViewController {
let viewModel: ViewModel
let customView: View
init(view: View, viewModel: ViewModel) {
self.viewModel = viewModel
self.customView = view
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
view = customView
}
override func viewDidLoad() {
super.viewDidLoad()
(self as? KeyboardScrollManagable)?.doSomething() //ERROR:
//Protocol 'KeyboardScrollManagable' can only be used as a generic constraint because it has Self or associated type requirements
}
}
protocol Scrollable: UIView {
var scrollView: UIScrollView { get }
}
final class AbcView: UIView, Scrollable {
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .clear
return scrollView
}()
//rest of the impl
}
final class AbcViewController: ViewController<AbcViewModel, AbcView> {
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension AbcViewController: KeyboardScrollManagable {}
I'm stuck! Any ideas? Or am I asking too much from the Swift?
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()
}
}
}
}
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
{
}
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
}
}
I have a UIScrollView with a UIView inside of it. When I try to manually resize the UIView by code, it doesn't work. I tried to set the autoResizingMask property of the UIView to .None and I implemented the touchesShouldCancelInContentView method of the UIScrollView to return true but it still doesn't work. Here's my code :
class ViewController: UIViewController {
#IBOutlet var scrollView: CustomScrollView!
#IBOutlet var field: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
var gesture = UITapGestureRecognizer(target: self, action: "gestureHandler")
gesture.numberOfTapsRequired = 1
view.addGestureRecognizer(gesture)
scrollView.contentSize = CGSizeMake(430, 140)
field.autoresizingMask = .None
}
func gestureHandler() {
field.frame.size.width += 20
var view = UIView(frame: CGRectMake(0, 0, 30, 100))
view.backgroundColor = UIColor.redColor()
field.frame.size.width -= 30
field.frame.origin.x += 30
scrollView.addSubview(view)
}
}
class CustomScrollView: UIScrollView {
override func touchesShouldCancelInContentView(view: UIView!) -> Bool {
return true
}
}
What I get :
What I want :
Thanks for your help !