Access variable in ViewController from a different class in Swift - ios

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!

Related

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

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
}
}

How to move UIImageView using drag event

I would like to ask you about moving UIImageView.
Let’s imagine that an UIImageView is at location X1. If I want to move imv to location X2 using drag event, How can I implement it?
Please give me advice.
Thanks for reading.
I have refered to How to drag UIImageView using touches method
import UIKit
class ViewController: UIViewController {
#IBOutlet var imgView: UIImageView!
var imgMarker:UIImage?
var location = CGPoint()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
imgMarker = UIImage(named: "marker.png")
imgView.image = imgMarker
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?){
if let touch = touches.first {
location = touch.location(in: self.view)
imgView.center = location
}
}
}

Why does my program not work properly when trying to directly set my variable to a random integer using Int.random()?

I am following an online tutorial and when trying to directly set my randomBallNumber variable to a random number between 1 and 4 using randomBallNumber = Int.random(in: 0...4), my code doesn't work. However, when I first use var randomBallNumber = 0 and then change its value to a random one inside of a function, it runs properly.
Works:
class ViewController: UIViewController {
let ballArray = ["ball1", "ball2", "ball3", "ball4", "ball5"]
var randomBallNumber = 0
func newBallImage() {
ImageView.image = UIImage(named: ballArray[randomBallNumber])
randomBallNumber = Int.random(in: 0...4)
}
#IBOutlet weak var ImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
newBallImage()
}
#IBAction func askButtonPressed(_ sender: Any) {
newBallImage()
}
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
newBallImage()
}
}
Doesn't work:
class ViewController: UIViewController {
let ballArray = ["ball1", "ball2", "ball3", "ball4", "ball5"]
var randomBallNumber = Int.random(in: 0...4)
func newBallImage() {
ImageView.image = UIImage(named: ballArray[randomBallNumber])
}
#IBOutlet weak var ImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
newBallImage()
}
#IBAction func askButtonPressed(_ sender: Any) {
newBallImage()
}
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
newBallImage()
}
}
The only console error I am getting is:
2019-07-10 07:22:45.328092+0300 8BallPool[86631:3716368] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
and it shows for both variants.
The expected result would be for the image to change when the Ask! button is pressed which it doesn't when using the second piece of code.
var randomBallNumber = Int.random(in: 0...4)
This will be initialized once, and after that the value of randomBallNumber will not change unless you change it again.
So instead of making it as a global variable better make a function like:
func getRandomNumber() -> Int {
return Int.random(in: 0...4)
}
And call it wherever you need the randomBallNumber, like:
ImageView.image = UIImage(named: ballArray[getRandomNumber()])
Note: Always declare variable and outlet names in camelCase, where the first character should be small. Here your ImageView is a very wrong name, instead name it something like randomBallImageView.

Changing a Views textLabel from a different View

I have a custom class called colorChangerViewClass that inherits from UIView:
class colorChangerViewClass: UIView {
#IBOutlet weak var controller: ViewController!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// I want to change a textLabel in the main ViewController based on the current finger position here!
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// And here aswell!
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { ... }
func setColor(color: UIColor) {
self.backgroundColor = color
}
}
Inside the touchesBegan and touchesMoved methods I want to change a textLabel in the main ViewController (a different one) based on the current finger position.
What is the best way to establish communication between the two classes colorChangerViewClass and ViewController?
In your case you are trying to communicate child to parent, there are several ways from which you can gain desired out put.
1.Delegation
2. Notification
3. Passing Parent instance object to child (similar as delegate)
I am demonstrating with delegate, you can use delegation as:-
1. Declare
Declare a protocol somewhere out side your ColorChangerViewClass
protocol ColorChangerViewClassDelegate:class {
func fingerDidMoved()
}
2. Create delegate var inside your ColorChangerViewClass
class ColorChangerViewClass: UIView {
//declare delegate var
weak var delegate:ColorChangerViewClassDelegate?
#IBOutlet weak var controller: ViewController!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// I want to change a textLabel in the main ViewController based on the current finger position here!
self.notify()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
self.notify()
// And here aswell!
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
func setColor(color: UIColor) {
self.backgroundColor = color
}
func notify() {
if let delegate = self.delegate {
delegate.fingerDidMoved()
}
}
}
3. Set Delegate of your view to the controller
class SomeVC:UIViewController,ColorChangerViewClassDelegate {
var myLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
//your view
let theView = ColorChangerViewClass() // you might need auto layout or frame declared on your view to define view size
theView.delegate = self
}
//MARK:-deleate method of view
func fingerDidMoved() {
// set text from here
self.myLabel.text = "Your text"
}
}
You are too close to solve the problem, you need to set the reference of your ViewController to controller property of your colorChangerViewClass. Also no need to declare controller as outlet so change its declaration like below.
var controller: ViewController?
Now in touchesBegan and touchesMoved use this controller to access textLabel
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.controller?.textLabel.text = //set text
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
self.controller?.textLabel.text = //set text
}
Now in your ViewController class where you are creating object of colorChangerViewClass set the controller property of it.
#IOutlet var colorView: colorChangerViewClass!
//set controller property in viewDidLoad
colorView.controller = self
Note: class name always start with Uppercase letters so it would be better if you changed your class name to ColorChangerViewClass from colorChangerViewClass.

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))

Resources