Protocol/Delegate function doesn't trigger (Swift) [duplicate] - ios

I have an IBOutlet-connected UIView object (innerView) sitting over view controller (UIViewController)'s view. I want to let the user rotate innerView with their finger. So I have a subclass (TouchView) of UIView set to innerView's class. As the user rotates the view object, I want the view controller to receive a value from TouchView. And what I have is the following.
// UIView
protocol TouchDelegate: class {
func degreeChanged(degree: Double)
}
class TouchView: UIView {
weak var delegate: TouchDelegate? = nil
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let aTouch: AnyObject = touches.first! as UITouch
let newLoc = aTouch.location(in: self.superview)
...
...
let degree = { (radians: Double) -> Double in
return radians * 180 / M_PI
}
self.delegate?.degreeChanged(degree: degree)
}
}
// UIViewController
class ViewController: UIViewController, TouchDelegate {
#IBOutlet weak var innerView: UIView!
// MARK: - View
override func viewDidLoad() {
super.viewDidLoad()
let touchView = TouchView()
touchView.delegate = self
}
// MARK: - From TouchView
func degreeChanged(degree: Double) {
print(degree) // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< no call
}
}
Well, the view controller never receives a delegate call from TouchView. What am I doing wrong? Thank you for your help.

I see 2 problems
You created a new instance of touchView and did not add it into your view controller's views
Your innerView is not an instance of touchView and its delegate is not set
My approach to your situation would look something like this:
// UIViewController
class ViewController: UIViewController, TouchDelegate {
#IBOutlet weak var innerView: TouchView!
// MARK: - View
override func viewDidLoad() {
super.viewDidLoad()
innerView.delegate = self
}
// MARK: - From TouchView
func degreeChanged(degree: Double) {
print(degree) // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< no call
}
}

Related

Swift - Newbie question. Passing variable values between different files, functions and views

newbie swift programmer here that needs help with something that probably is trivial...
This is in different File, different View:
class CanvasContainerView: UIView {
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// here are all of the calculations that lead to scoreString being calculated..
var scoreString = round(1000 * accuracyTotal / angleTotal)
}
//lots of other code
}
I need the value of my calculated "scoreString" to be accessible in a different file, different controller. How can i pass this value through if i'm using TouchesMoved function ? Below is how i tried to implement it in different file but failed miserably as there is no "scoreString" in scope:
import UIKit
class CanvasMainViewController: UIViewController {
#IBOutlet weak var ScoreText: UITextField!
/// Prepare the drawing canvas.
/// - Tag: CanvasMainViewController-viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
ScoreText.text = "\(CanvasContainerView.touchesMoved(scoreString))"
}
//lots of code
}
In the end i want my score to be displayed on top of the screen:
So i am aware that there are questions like this already on Stack but whats specific about my question is that im using this "touchesMoved" overriden function that doesn't let me return any value. It shouts it needs void return type.
I will be very thankful for any help.
Using a delegate is the good method :
// the protocol (ie the method called by container view in the controller)
protocol CanvasContainerViewDelegate {
func scoreUpdate(fromView view:CanvasContainerView, newScore: Double)
}
class CanvasContainerView: UIView {
// the delegate that will be called
var delegate: CanvasContainerViewDelegate?
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// here are all of the calculations that lead to scoreString being calculated..
var scoreString = round(1000 * accuracyTotal / angleTotal)
// Telling the delegate the new score value
delegate?.scoreUpdate(fromView: self, newScore: scoreString)
}
//lots of other code
}
class CanvasMainViewController: UIViewController, CanvasContainerViewDelegate {
#IBOutlet weak var ScoreText: UITextField!
#IBOutlet weak var containerView: CanvasContainerView!
/// Prepare the drawing canvas.
/// - Tag: CanvasMainViewController-viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// set the controller as a delegate of the view
containerView.delegate = self
}
// Update the score (called by the view)
func scoreUpdate(fromView view: CanvasContainerView, newScore: Double) {
ScoreText.text = "\(newScore)"
}
//lots of code
}
Apparently adding public in front of the var declared on top of the file solved the problem. Thank you for participation in trying to help me.

i want to triger navigationcontroller when i press button in UIView class

I want to trigger Navigation controller to some other screen when i press the button in UIView class. How can i do this?
//Code for UIView Class in Which Button Iboutlet is created
import UIKit
protocol ButtonDelegate: class {
func buttonTapped()
}
class SlidesVC: UIView {
var delegate: ButtonDelegate?
#IBAction func onClickFinish(_ sender: UIButton) {
delegate?.buttonTapped()
}
#IBOutlet weak var imgProfile: UIImageView!
}
//ViewController Class code in Which Button Protocol will be entertained
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
var slidesVC = SlidesVC()
override func viewDidLoad() {
super.viewDidLoad()
slidesVC = SlidesVC()
// add as subview, setup constraints etc
slidesVC.delegate = self
}
extension BaseVC: ButtonDelegate {
func buttonTapped() {
self.navigationController?.pushViewController(SettingsVC.settingsVC(),
animated: true)
}
}
A more easy way is to use typealias. You have to write code in 2 places. 1. your viewClass and 2. in your View Controller.
in your SlidesView class add a typealias and define param type if you need otherwise leave it empty.
class SlidesView: UIView {
typealias OnTapInviteContact = () -> Void
var onTapinviteContact: OnTapInviteContact?
#IBAction func buttonWasTapped(_ sender: UIButton) {
if self.onTapinviteContact != nil {
self.onTapinviteContact()
}
}
}
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let slidesView = SlidesView()
slidesView.onTapinviteContact = { () in
// do whatever you want to do on button tap
}
}
You can use the delegate pattern to tell the containing ViewController that the button was pressed and let it handle whatever is needed to do next, The view doesn't really need to know what happens.
A basic example:
protocol ButtonDelegate: class {
func buttonTapped()
}
class SomeView: UIView {
var delegate: ButtonDelegate?
#IBAction func buttonWasTapped(_ sender: UIButton) {
delegate?.buttonTapped()
}
}
class ViewController: UIViewController {
var someView: SomeView
override func viewDidLoad() {
someView = SomeView()
// add as subview, setup constraints etc
someView.delegate = self
}
}
extension ViewController: ButtonDelegate {
func buttonTapped() {
self.showSomeOtherViewController()
// or
let vc = NewViewController()
present(vc, animated: true)
}
}

moving data between classes in swift 4

I'm new to swift and am making an graphing app with two views where one has a text field to enter data and the other has a view to display the data. I've got the data as two arrays of doubles in my ViewController class, but I can't move the data to the class of UIView where I want to draw to the view it because it does not inherit the arrays. I've tried accessor methods but that hasn't changed anything.
Here is the class that contains xValues and yValues
class ViewController: UIViewController {
#IBOutlet var DataEntry: UITextView!
var xValues = Array<Double>()
var yValues = Array<Double>()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//the xValues and yValues arrays are filled when the view changes on the press of a button
}
public func getXCord() -> Array<Double>{
return xValues
}
public func getYCord() -> Array<Double>{
return yValues
}
}
Here is the class I want them delivered to. I'm getting an error when initializing xCords and yCords to ViewController.getXCord() and ViewController.getYCord() respectively.
class GraphView: UIView{
var points: [Points] = []
var xCords: Array<Double> = ViewController.getXCord()
var yCords: Array<Double> = ViewController.getYCord()
var position = CGPoint(x: 0,y: 0)
}
You are doing this the complete opposite way.
In the MVC pattern, the view, your GraphView class, should never directly talk to the controller. Instead, the view should use a delegate and/or a datasource to communicate with the controller.
Your view should have a GraphViewDatasource:
protocol GraphViewDatasource : class {
func xValues(inGraph: GraphView) -> [Double]
func yValues(inGraph: GraphView) -> [Double]
}
// in GraphView
weak var datasource: GraphViewDatasource?
func reloadData() {
guard let datasource = self.datasource else { return }
xValues = datasource.xValues(inGraph: self)
yValues = datasource.yValues(inGraph: self)
// redraw the graph...
}
Your controller should implement GraphViewDatasource:
class ViewController: UIViewController, GraphViewDatasource {
func xValues(inGraph: GraphView) -> [Double] { return self.xValues }
func yValues(inGraph: GraphView) -> [Double] { return self.yValues }
}
and set self as the data source of the graph view:
let graph = GraphView(frame ...)
self.view.addSubView(graph)
graph.datasource = self
graph.reloadData()
You need to pass xCoords and yCoords to GraphView from ViewController.
First, initialize xCoords and yCoords with empty array:
class GraphView: UIView{
var points: [Points] = []
var xCords: Array<Double> = []
var yCords: Array<Double> = []
var position = CGPoint(x: 0,y: 0)
}
Than pass it from ViewController:
class ViewContoller: UIViewController {
#IBOutlet var graphView: GraphView!
override func viewDidLoad() {
super.viewDidLoad()
graphView.xCoords = self.xCoords
graphView.yCoords = self.yCoords
}
}

How to call subclass method from superclass with protocol conformance

I'm creating swift UI component that will provide some functionality to the UIView and UICollectionViewCell. I want to make it easy to customise. (code here are of course a simplification) In code below I will do some hard work in layoutSubview (I have to override layoutSubviews for each custom class because in extension we can't override class methods) and then call the add method with default implementation that should be easy to change its behaviour if needed.
Problem is that creating SubClassView instance calls correctly CustomView.layoutSubviews but inside this method SomeProtocol.add from extension is called instead of SubClassView.add. I understand that this is by Swift design but how can achieve that developer of my component without overriding layoutSubviews that I prepared for him, overrides only that one add method prepared by me for customisation with default implementation that is ready for him if he is not interested in changing it.
protocol SomeProtocol: class {
var property: Int { get set }
func add() // Method that can be override for custom behaviour
}
extension SomeProtocol where Self: UIView {
// Default implementation
func add() {
property += Int(frame.width)
print("SomeProtocol.add [property: \(property)]")
}
}
class CustomView: UIView, SomeProtocol {
var property: Int = 1
override func layoutSubviews() {
super.layoutSubviews()
print("CustomView.layoutSubviews")
// Some specific high end code for CustomView
add()
}
}
class CustomCollectionViewCell: UICollectionViewCell, SomeProtocol {
var property: Int = 2
override func layoutSubviews() {
super.layoutSubviews()
print("CustomCollectionViewCell.layoutSubviews")
// Some specific high end code for CustomCollectionViewCell
add()
}
}
class SubClassView: CustomView { // Class that can implemented by the third party developer for customisation
// This method will not be called
func add() {
property += 10
print("SubClassView.add [property: \(property)]")
}
}
let view = SubClassView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
// prints:
// CustomView.layoutSubviews
// SomeProtocol.add [property: 101]
You can add indirect class between yours CustomView and SomeProtocol, lets name it CustomViewBase. This class should inherit UIView and implement SomeProtocol. Now make your class CustomView subclass of CustomViewBase and add to it implementation of add() method and call from it super.add().
Here is code:
protocol SomeProtocol: class {
var property: Int { get set }
func add() // Method that can be override for custom behaviour
}
extension SomeProtocol where Self: UIView {
// Default implementation
func add() {
property += Int(frame.width)
print("SomeProtocol.add [property: \(property)]")
}
}
class CustomViewBase: UIView, SomeProtocol {
var property: Int = 1
}
class CustomView: CustomViewBase {
func add() {
super.add()
}
override func layoutSubviews() {
super.layoutSubviews()
print("CustomView.layoutSubviews 2")
// Some specific high end code for CustomView
add()
}
}
class CustomCollectionViewCell: UICollectionViewCell, SomeProtocol {
var property: Int = 2
override func layoutSubviews() {
super.layoutSubviews()
print("CustomCollectionViewCell.layoutSubviews")
// Some specific high end code for CustomCollectionViewCell
add()
}
}
class SubClassView: CustomView { // Class that can implemented by the third party developer for customisation
// This method will not be called
override func add() {
property += 10
print("SubClassView.add [property: \(property)]")
}
}
let view = SubClassView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
First, let's move all the animation stuff into its own thing.
struct CustomAnimator {
var touchesBegan: (UIView) -> Void = { $0.backgroundColor = .red() }
var touchesEnded: (UIView) -> Void = { $0.backgroundColor = .clear() }
}
So this is how we want to animate things. When touches begin, we'll make them red. When touches end, we'll make them clear. This is just an example, and of course can be overridden; these are just default values. This may not be the most convenient data structure for you; you could come up with lots of other ways to store this. But the key is that it's just a struct that animates the thing it is passed. It's not a view itself; there's no subclassing. It's just a value.
So, how can we use this? In cases where it's convenient to subclass, we can use it this way:
class CustomDownButton: UIButton {
var customAnimator: CustomAnimator?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
customAnimator?.touchesBegan(self)
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
customAnimator?.touchesEnded(self)
super.touchesEnded(touches, with: event)
}
}
So if you feel like subclassing your view, you can just assign a CustomAnimator onto it and it'll do the thing. But that's not really the interesting case, IMO. The interesting case is when it's not convenient to subclass. We want to attach this to a normal button.
UIKit already gives us a tool for attaching touch behavior to random views without subclassing them. It's called a gesture recognizer. So let's build one that does our animations:
class CustomAnimatorGestureRecognizer: UIGestureRecognizer {
let customAnimator: CustomAnimator
init(customAnimator: CustomAnimator) {
self.customAnimator = customAnimator
super.init(target: nil, action: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if let view = view {
customAnimator.touchesBegan(view)
}
super.touchesBegan(touches, with: event)
}
override func reset() {
// I'm a bit surprised this isn't in the default impl.
if self.state == .possible {
self.state = .failed
}
super.reset()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
if let view = view {
customAnimator.touchesEnded(view)
}
self.reset()
super.touchesEnded(touches, with: event)
}
}
It should be pretty obvious how this works. It just calls the animator to modify our view any time touches start or stop. There's a little bit of bookkeeping needed to properly handle the state machine, but it's very straightforward.
There is no step three. Setting this up is trivial:
override func viewDidLoad() {
super.viewDidLoad()
customButton.customAnimator = CustomAnimator()
normalButton.addGestureRecognizer(CustomAnimatorGestureRecognizer(customAnimator: CustomAnimator()))
}
Full project on GitHub.
I play with this and I came up with 2 different approaches.
(github with playground -> https://github.com/nonameplum/Swift-POP-with-Inheritance-problem)
Composition
struct BaseFunctionality<T: UIView where T: SomeProtocol> {
var add: (T) -> Void
init() {
add = { (view) in
view.property += Int(view.frame.width)
print("BaseFunctionality [property: \(view.property)]")
}
}
}
protocol SomeProtocol: class {
var property: Int { get set }
}
class CustomView: UIView, SomeProtocol {
var property: Int = 1
var baseFunc = BaseFunctionality<CustomView>()
override func layoutSubviews() {
super.layoutSubviews()
print("CustomView.layoutSubviews")
// Some specific high end code for CustomView
baseFunc.add(self)
}
}
class CustomCollectionViewCell: UICollectionViewCell, SomeProtocol {
var property: Int = 2
var baseFunc = BaseFunctionality<CustomCollectionViewCell>()
override func layoutSubviews() {
super.layoutSubviews()
print("CustomCollectionViewCell.layoutSubviews")
// Some specific high end code for CustomCollectionViewCell
baseFunc.add(self)
}
}
class SubClassView: CustomView { // Class that can implemented by the third party developer for customisation
override init(frame: CGRect) {
super.init(frame: frame)
self.baseFunc.add = { (view) in
view.property -= Int(view.frame.width)
print("SubClassView [property: \(view.property)]")
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let view = SubClassView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
Kind of delegates
protocol Functionality: class {
func add()
}
// Default implementation
extension SomeProtocol where Self: UIView {
func add() {
property = -5
print("Functionality.add [property = \(property)]")
}
}
protocol SomeProtocol: class {
var property: Int { get set }
var delegate: Functionality? { get set }
}
class CustomView: UIView, SomeProtocol {
var property: Int = 1
weak var delegate: Functionality?
override func layoutSubviews() {
super.layoutSubviews()
print("CustomView.layoutSubviews")
// Some specific high end code for CustomView
if let delegate = delegate {
delegate.add()
} else {
self.add()
}
}
}
class CustomCollectionViewCell: UICollectionViewCell, SomeProtocol {
var property: Int = 2
weak var delegate: Functionality?
override func layoutSubviews() {
super.layoutSubviews()
print("CustomCollectionViewCell.layoutSubviews")
// Some specific high end code for CustomCollectionViewCell
if let delegate = delegate {
delegate.add()
} else {
self.add()
}
}
}
class SubClassView: CustomView { // Class that can implemented by the third party developer for customisation
override init(frame: CGRect) {
super.init(frame: frame)
self.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SubClassView: Functionality {
func add() {
property = 5
print("SubClassView.add [property = \(property)]")
}
func doNothing() { print("SubClassView.doNothing [\(property)]") }
}
let view = SubClassView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))

Access variable in ViewController from a different class in Swift

I have a slider sliderLineSize and a variable lineSize in a ViewController. The UISlider sliderLineSize changes lineSize. However, lineSize actually used in the drawRect section of the viewLine class which attaches to a UIView.
Question:
How do I pass or make accessible the variable lineSize which is set in the ViewController to the class viewLine where it is used in drawRect?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
#IBOutlet weak var myImageView: UIImageView!
var lineSize: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
myImageView.alpha = 0.5
}
#IBAction func sliderLineSize(sender: UISlider) {
lineSize = Int(sender.value)
}
}
class viewLine: UIView {
let path=UIBezierPath()
var incrementalImage:UIImage?
var previousPoint:CGPoint = CGPoint.zero
var strokeColor:UIColor?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
incrementalImage?.drawInRect(rect)
path.lineWidth = lineSize
path.stroke()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
let currentPoint = touch!.locationInView(self)
path.moveToPoint(currentPoint)
previousPoint=currentPoint
self.setNeedsDisplay()
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
let currentPoint = touch!.locationInView(self)
let midPoint = self.midPoint(previousPoint, p1: currentPoint)
path.addQuadCurveToPoint(midPoint,controlPoint: previousPoint)
previousPoint=currentPoint
path.moveToPoint(midPoint)
self.setNeedsDisplay()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.drawBitmap()
self.setNeedsDisplay()
path.removeAllPoints()
}
func midPoint(p0:CGPoint,p1:CGPoint)->CGPoint {
let x=(p0.x+p1.x)/2
let y=(p0.y+p1.y)/2
return CGPoint(x: x, y: y)
}
func drawBitmap() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 1)
strokeColor?.setStroke()
if((incrementalImage) == nil){
let rectPath:UIBezierPath = UIBezierPath(rect: self.bounds)
UIColor.whiteColor().setFill()
rectPath.fill()
}
incrementalImage?.drawAtPoint(CGPointZero)
path.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
There are two main ways to do this.
Option 1:
Give your ViewLine class its own lineSize property:
class ViewLine: UIView {
var lineSize = 1
}
Give ViewController a reference to ViewLine, and use a property observer to update the property inside viewLine whenever it changes in ViewController:
class ViewController: UIViewController {
// v~~~ be sure to connect this outlet in interface builder
#IBOutlet weak var viewLine: ViewLine!
var lineSize = 1 {
didSet {
viewLine.lineSize = lineSize
}
}
}
Now your ViewLine class will have its own lineSize property that can be accessed from within its drawRect method directly.
Option 2:
Give your ViewLine class a reference to ViewController:
class ViewLine: UIView {
// v~~~ be sure to connect this outlet in interface builder
#IBOutlet weak var controller: ViewController!
}
Now, in your drawRect method, replace path.lineWidth = lineSize with path.lineWidth = controller.lineSize.
Basically, one of your classes needs a reference to the other in order for them to be able to communicate.
You should make Singleton Model class. A singleton class can be accessed from anywhere. Here is how you should create a singleton class in swift.
class ApplicationModel {
class var sharedInstance: ApplicationModel {
get {
struct Static {
static var instance: ApplicationModel? = nil
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token, {
Static.instance = ApplicationModel()
})
return Static.instance!
}
}
var lineSize = 1
}
Inside ViewController
override func viewDidLoad() {
super.viewDidLoad()
//Instantiate ApplicationModel
//GET
let lineSize = ApplicationModel.sharedInstance.lineSize
//SET
ApplicationModel.sharedInstance.lineSize = 5
}
Inside viewLine
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Access Application Model
//GET
let lineSize = ApplicationModel.sharedInstance.lineSize
//SET
ApplicationModel.sharedInstance.lineSize = 5
}
Hope this helps!

Resources