Why custom delegate protocol contains an optional value - SWIFT - ios

I am trying to create a custom delegate that sends an integer value to the main parent ViewController class. I set up a protocol and implemented on the parent class. The optional binding code always shows an optional value. Why is this happening although i have set the delegate value from my UIViewController class?
protocol SendMessage{
func sendViewMessage(Int)
}
class BankLoginView: UIView,UITextFieldDelegate {
var delegate1 : SendMessage?
// var accountViewController:UIViewController=AccountLanding()
override init(frame: CGRect) {
super.init(frame: frame)
}
#IBAction func btnTickAction(sender: AnyObject) {
if let temp = self.delegate1 {
delegate1?.sendViewMessage(2)
}else{
println("optional value contains nill value")
}
}
}
and i am setting the value of delegate as
class BankLogin: UIViewController ,SendMessage{
override func viewDidLoad() {
super.viewDidLoad()
let rect: CGRect = CGRect (x: 0, y :10 , width: self.view.frame.size.width-50, height: self.view.frame.size.height-10)
var a = BankLoginView(frame : rect)
a.delegate1 = BankLogin()
}
}

What you are doing is creating a new view of class BankLoginView
var a = BankLoginView(frame : rect)
The delegate of it will be a new BankLogin instance:
a.delegate1 = BankLogin()
When viewDidLoad() finishes var a is destroyed cause is not retained with any object.
Probably in the storyboard or nib file you've setted the view class of BankLogin as BankLoginView, but that view is not the same of that one you created in this line:
var a = BankLoginView(frame : rect)
Therefore, the delegate is not assigned and it prints "optional value contains nill value"
Declare BankLoginView as IBOutlet property:
#IBOutlet weak var bankLoginView: BankLoginView? (ensure the view is linked with the nib file)
and then in viewDidLoad() do:
bankLoginView.delegate1 = self; (you can do this in Interface Builder too)

A couple of things i noticed abut this code.
First you are unwrapping your optional delegate variable. but then using the optional variable rather than the unwrapped one. I would call the delegate like this:
#IBAction func btnTickAction(sender: AnyObject) {
if let actualDelegate = self.delegate1 {
actualDelegate.sendViewMessage(2)
}else{
println("optional value contains nill value")
}
Secondly you look like you are creating a new instance of the BankLogin view controller and setting that as the the delegate.
class BankLogin: UIViewController ,SendMessage{
override func viewDidLoad() {
super.viewDidLoad()
let rect: CGRect = CGRect (x: 0, y :10 , width: self.view.frame.size.width-50, height: self.view.frame.size.height-10)
var a = BankLoginView(frame : rect)
//Do you want to create a new instance of Bank login here?
//a.delegate1 = BankLogin()
// Perhaps should be
a.delegate1 = self
}

Related

Changing class in a UIView (Swift)

I would like to use a button to toggle the contents of a UIView.
I set up two classes, graphClass1 and graphClass2. (The button is in the topView.)
When I click the button, I get the "my button" message from debugPrint, but I don't get the debugPrint messages from within the classes. So, I added setNeedsDisplay but that did not help.
(This is a simplified version - there are actually a lot more classes - which is why I am trying to reuse the same view instead of just creating two separate views.)
how do I get the appropriate class to display in the view?
because graphClass1 creates additional subviews when I toggle back and forth, will the number of graphClass1's subviews just keep growing? If so, how do I remove them when leaving? (I know that self.layer.sublayers = nil or textView.removeAll() would leave them until returning - if they even remove them at all.)
in the button toggle, rather than use a Bool to test which graph, I'd prefer something more intuitive like if currentGraph == GraphClass1 but this gives me the error message: Binary operator '==' cannot be applied to operands of type 'UIView' and 'GraphClass1.Type'. How would do I fix this?
class ViewController: UIViewController {
#IBOutlet var topView: UIView!
#IBOutlet var bottomView: UIView!
#IBOutlet var myButton: UIButton!
var graph1: Bool = true
var currentView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
bottomView = GraphClass1()
setConstraints()
}
#IBAction func myButton(_ sender: Any) {
debugPrint("my button")
if graph1 {
currentView.removeFromSuperview()
currentView = GraphClass2()
cv2.addSubview(currentView)
graph1 = false
}
else {
currentView.removeFromSuperview()
currentView = GraphClass1()
cv2.addSubview(currentView)
graph1 = true
}
cv2.setNeedsDisplay()
}
}
class GraphClass1: UIView {
var textView = UITextView()
override func draw(_ rect: CGRect) {
super.draw(rect)
self.layer.sublayers = nil
textView.removeAll()
createTextView()
debugPrint("inside GraphClass1")
}
func createTextView() {
textView = UITextView(frame: CGRect(x: 8, y: 8, width: 300, height: 100))
textView.text = "Test, this is only a test"
textView.textAlignment = .center
textView.font = UIFont(name: "Courier", size: 16)
textView.backgroundColor = .orange
self.addSubview(textView)
}
}
class GraphClass2: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
debugPrint("inside GraphClass2")
}
}
Instead of changing the UIView() to a GraphClass(), you should add GraphClass() to bottomView() as a subview. To switch out GraphClass1(), you would remove it from bottomView() and set up GraphClass2() as the subview of bottomView(). Removing a view also removes all its subviews.
First, create a bottomView in interface builder, just like the top view. It will be like a place holder. You will have;
#IBOutlet var bottomView: UIView!
In viewDidLoad, you add the specific view as needed with
bottomView.addSubView(myInstanceOfAGraphView.view)
myInstanceOfAGraphView.didMove(toParent: bottomView)
I would create view controllers for each graph view and switch them as needed.
When you need to change, remove its view with;
myInstanceOfAGraphView.view.removeFromSuperview()

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

Programmatically add buttons to array before viewDidLoad in swift

When using the storyboard, you can draw 2 buttons onto it, and add then add them to an array by creating:
#IBOutlet private var cardButtons: [UIButton]!
This outlet collection is now an array of both buttons that loads before viewDidLoad as a variable.
How do I create this array of buttons programmatically without using storyboard?
I've tried this but it gives me a declaration error:
class ViewController: UIViewController {
var cardButtons = [CardView]()
let cardOne = CardView()
cardButtons.append(cardOne)
...rest of viewController standard class
}
There a several points of time in the ViewController's lifecycle where you might want to fill the cardButtons array. For example you could do this in viewDidLoad.
class ViewController: UIViewController {
var cardButtons = [CardView]()
override func viewDidLoad() {
super.viewDidLoad()
let cardOne = CardView()
cardButtons.append(cardOne)
}
}
Keep in mind that viewDidLoad is called every time the View is loaded into memory. In my example cardOne would be recreated every time. To avoid this you could store cardOne in a instance var, as you did initially.
class ViewController: UIViewController {
var cardButtons = [CardView]()
let cardOne = CardView()
override func viewDidLoad() {
super.viewDidLoad()
cardButtons.append(cardOne)
}
}
As I said, there several points of time in the ViewController's lifecycle where you might want to fill the cardButtons array. Other functions could be:
viewDidAppear(), to fill the area every time the view appears.
init(), if you are not using storyboard at all.
Here is what I ended up doing:
class ViewController: UIViewController {
let uiView: UI = {
var ui:UI = UI()
let cardOne = CardView()
let cardTwo = CardView()
ui.addButton(item: cardOne)
ui.addButton(item: cardTwo)
return ui
}()
...rest of viewController standard class
}
class UI {
var cardButtons = [CardView]()
func addButton(button: CardView){
cardButtons.append(button)
}
}

Class 'ProductDetailViewController' has no initializers

I am trying to create scrollable imageview. Currently using swift 4.0. While running I am getting "has no initializers" error. I am not sure what code brought this error. I am new to swift and I am unable to track what is going on.
Class 'ProductDetailViewController' has no initializers I am getting this error.
Can any one please tell me how to fix this error.
Entire view controller code is given below. Thanks in advance
import UIKit
import SwiftyJSON
class ProductDetailViewController: UIViewController,UIScrollViewDelegate {
//Outlers
#IBOutlet weak var scrollView: UIScrollView!
//Global variables
var productDict : JSON = []
var arrayOfProductImageUrls : Array<Any> = []
var zoomScroll : UIScrollView
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.initialSetup()
self.scrollViewSetup()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
//Mark: - Scrollview Setup
func initialSetup() {
self.scrollView.delegate = self
}
func scrollViewSetup() {
let productsArray : Array = self.arrayOfProductImageUrls;
self.scrollView.backgroundColor = UIColor.white
var frame : CGRect = CGRectMake(0, 0, self.scrollView.frame.size.width, 360)
for (index, value) in productsArray.enumerated() {
//Imageview Setup
let productImageView : UIImageView
let width : NSInteger = Int(frame.width)
productImageView.frame.origin.x = CGFloat(Int (width * index))
productImageView.frame.origin.y = 0
//Scrollview Setup
zoomScroll.frame = frame;
zoomScroll.backgroundColor = UIColor.clear
zoomScroll.showsVerticalScrollIndicator = false
zoomScroll.showsHorizontalScrollIndicator = false
zoomScroll.delegate = self
zoomScroll.minimumZoomScale = 1.0
zoomScroll.maximumZoomScale = 6.0
zoomScroll.tag = index
zoomScroll.isScrollEnabled = true
self.scrollView.addSubview(zoomScroll)
//Setting image
let imageUrl : URL = (productsArray[index] as AnyObject).url
productImageView.sd_setImage(with: imageUrl)
if index < productsArray.count {
productImageView.frame = zoomScroll.bounds
productImageView.contentMode = UIViewContentMode.redraw
productImageView.clipsToBounds = true
productImageView.backgroundColor = UIColor.clear
zoomScroll.addSubview(productImageView)
}
self.scrollView.contentSize = CGSizeMake(CGFloat(frame.origin.x) + CGFloat(width), productImageView.frame.size.height)
}
}
//Mark : Custom methods
func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect {
return CGRect(x: x, y: y, width: width, height: height)
}
func CGSizeMake(_ width: CGFloat, _ height: CGFloat) -> CGSize {
return CGSize(width: width, height: height)
}
}
#Saurabh Prajapati. Nice and quick catch.
For more, The Swift Programming Language states
Classes and structures must set all of their stored properties to an
appropriate initial value by the time an instance of that class or
structure is created. Stored properties cannot be left in an
indeterminate state.
You can set an initial value for a stored property within an
initializer, or by assigning a default property value as part of the
property’s definition.
Let me explain more, the problem was with scrollView property that does not have a default value. As above mentioned, all variables in Swift must always have a value or nil (make it optional). When you make it optional, you allow it to be nil by default, removing the need to explicitly give it a value or initialize it.
Here we are using ! optional so the default value will be nil
class ViewController : UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
}
Setting nil as the default value
class ViewController : UIViewController {
#IBOutlet weak var scrollView: UIScrollView! = nil
}
Setting the default value, creating an object now it is not nil.
class ViewController : UIViewController {
#IBOutlet weak var scrollView: UIScrollView = UIScrollView()
}
But we can't-do like that, we are not setting the default value here.
class ViewController : UIViewController {
#IBOutlet weak var scrollView: UIScrollView
}
I hope it will help.
I had the same echo
it was so easy to solve it by using ! after any variable like this
var ref: DatabaseReference!

Swift: Delegation protocol not setting UILabel properly

I have the following Protocol:
protocol SoundEventDelegate{
func eventStarted(text:String)
}
which I call in this class:
class SoundEvent {
var text:String
var duration:Double
init(text: String, duration: Double){
self.text = text
self.duration = duration
}
var delegate : SoundEventDelegate?
func startEvent(){
delegate?.eventStarted(self.text)
}
func getDuration() -> Double{
return self.duration //TODO is this common practice?
}
}
Which I have my ViewController conform to:
class ViewController: UIViewController, SoundEventDelegate {
//MARK:Properties
#IBOutlet weak var beginButton: UIButton!
#IBOutlet weak var kleinGrossLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//DELEGATE method
func eventStarted(text:String){
kleinGrossLabel.text = text
}
//MARK: actions
#IBAction func startImprovisation(sender: UIButton) {
var s1:Sentence = Sentence(type: "S3")
var s2:Sentence = Sentence(type: "S1")
var newModel = SentenceMarkov(Ult: s1, Penult: s2)
s1.start()
beginButton.hidden = true
}
}
But when I run the app kleinGrossLabel.text does not change. Am I referring to the label in the wrong way? Or is it the way that I do delegation that is incorrect?
Here are links to the complete Class definitions of Sentence and SentenceMarkov
https://gist.github.com/anonymous/9757d0ff00a4df7a29cb - Sentence
https://gist.github.com/anonymous/91d5d6a59b0c69cba915 - SentenceMarkov
You never set the delegate property. It's nil. It will never be called.
First off it's not common practice to have a setter in swift. if you want to have a readonly property you can use private(set) var propertyName
in other cases simply access the property like mentioned in the comment
Also i don't see a reason why you eventArray in sentence is of type [SoundEvent?] not [SoundEvent] as SoundEventdoes not seem to have a failable initialiser
Like mentioned before you need to not only implement the SoundEventDelegate protocol but also set the delegate
the problem is that you can't really access the SoundEventDelegate from the viewcontroller because you instantiate the SoundEvents inside Sentence
var soundEventDelegate: SoundEventDelegate?
the easiest way to do this would be adding a soundEventDelegate property for sentence and setting it like this:
let s1:Sentence = Sentence(type: "S3")
let s2:Sentence = Sentence(type: "S1")
s1.soundEventDelegate = self
s2.soundEventDelegate = self
and inside sound you would need the set the delegate for every event to the soundEventDelegate of Sentence
you could do it like this:
var soundEventDelegate: SoundEventDelegate? = nil {
didSet {
eventArray.forEach({$0.delegate = soundEventDelegate})
}
}
or write another initialiser that takes the delegate
hope this helps
p.s: you shouldn't inherit form NSObject in swift excepts it's really necessary

Resources