Class 'ProductDetailViewController' has no initializers - ios

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!

Related

How to access the value of a UIControl from another UIView class

Beginner question. I have a UISlider on the storyboard, and in another UIView class besides the ViewController I would like to use the slider's value to change path/shape variables within the makeShape() function.
I have tried:
a) In OtherView, try to directly reference ViewController's global variable val. This results in "unresolved identifier"
b) In ViewController, try to assign mySlider.value to a variable declared in OtherView, otherVal. This results in "Instance member 'otherVal' cannot be used on type 'OtherView'".
Could someone please help me learn how to do this correctly?
ViewController.swift:
class ViewController: UIViewController {
var val: CGFloat = 0.0
#IBOutlet weak var mySlider: UISlider!
#IBOutlet weak var otherView: UIView!
#IBAction func mySliderChanged(_ sender: Any) {
val = CGFloat(mySlider.value)
setOtherVal()
}
func setOtherVal() {
otherView.otherVal = val
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let otherView = OtherView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 100, height: 100)))
self.view.addSubview(otherView)
}
}
OtherView.swift:
class OtherView: UIView {
var path: UIBezierPath!
var otherVal: CGFloat = 0.0
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
makeShape()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func makeShape() {
let width: CGFloat = self.frame.size.width
let height: CGFloat = self.frame.size.height
let path1 = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 50, height: 50)))
let shapeLayer1 = CAShapeLayer()
shapeLayer1.path = path1.cgPath
shapeLayer1.fillColor = UIColor.blue.cgColor
self.layer.addSublayer(shapeLayer1)
shapeLayer1.position = CGPoint(x: otherVal, y: height)
}
}
Right now you're trying to access the otherVal property in ViewController on the class OtherVal which is shared globally through the whole app, not the property on the instance of OtherVal you've created in ViewController.
The simplest fix here is to store the OtherView instance you created in viewDidAppear and use that property to update the view.
class ViewController: UIViewController {
var val: Float = 0.0
#IBOutlet weak var mySlider: UISlider!
// Add a property to hold on to the view after you create it.
var otherView: OtherView?
#IBAction func mySliderChanged(_ sender: Any) {
val = mySlider.value
setOtherVal()
}
func setOtherVal() {
// `val` is a Float and `otherVal` is a `CGFloat` so we have to convert first.
let newValue = CGFloat(self.val)
// Use the property that's holding your view to update with the new value from the slider.
self.otherView?.otherVal = newValue
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let otherView = OtherView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 100, height: 100)))
// Set the property to the view you just created so you can update it later.
self.otherView = otherView
self.view.addSubview(otherView)
}
}
Currently though it doesn't look like you're actually updating OtherView once the new value is set, so you'll want to do something similar there. Keep a reference to the CAShapeLayer you create and update the position when otherVal changes.
Also note viewDidAppear can be called multiple times throughout a view controller's life cycle. If you only want to add the view once, you should use viewDidLoad which will guarantee you have a freshly created self.view to work with.
Edit: As mentioned in the comments, you can also set up a custom view in the storyboard / nib and create an outlet. Right now it doesn't look like your code is set up to work with that though.

playing a sound file in a pageControler with Xcode and Swift

I would like to play a sound file when a new page comes on the screen.
So far I did that to have the pageControler working with an array of images.
Can someone tell me where I have to put my code in order to play an array of sound? So that the sound1 will be played when the image1 comes on the screen.
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
var images: [String] = ["1", "2", "3", "4", "5"]
var frame = CGRect(x:0,y:0, width:0, height:0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
pageControl.numberOfPages = images.count
for index in 0..<images.count {
frame.origin.x = scrollView.frame.size.width * CGFloat(index)
frame.size = scrollView.frame.size
let imgView = UIImageView(frame: frame)
imgView.image = UIImage(named: images[index])
self.scrollView.addSubview(imgView)
}
scrollView.contentSize = CGSize(width:(scrollView.frame.size.width * CGFloat(images.count)), height: scrollView.frame.size.height)
scrollView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(pageNumber)
}
}
I don't see a direct way to get called when the selected page changes. You could add code in your page view controller's setViewControllers(_:direction:animated:completion:) method that figures out which page is active and plays the appropriate sound.
If you use a page control you could probably also subclass UIPageControl and use a didSet on the currentPage property to figure out which sound to play.
Edit:
Just add a new file, make it a Cocoa touch class, and make it a subclass of UIPageControl. Name it CustomPageControl. Then your implementation can be as simple as this:
import UIKit
class CustomPageControl: UIPageControl {
override var currentPage: Int {
didSet {
//Your code to play sounds based on selected index could go
//here, or broadcast a notification that your view controller
//would listen for
print("New page index = \(currentPage)")
}
}
}
Then just select the page control on your UIPageViewController, select the "Identity Inspector", and change the class of the page control to your custom CustomPageControl class. Once you've done that, whenever your page index changes, the didSet method above will be called.

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 access the value of a UIStepper inside a Cocoa Touch Class

I'm trying to create a custom slider, using a cocoa touch class, whose maximum value is determined by a UIStepper. I have the UIStepper wired to my view controller and I want to reference its value inside the cocoa touch class as the slider's maximum value. What is the syntax for referencing the stepper's value inside the class?
I keep getting the error use of unresolved identifier.
Here is my viewController:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var stepperValue: UIStepper!
#IBOutlet weak var label: UILabel!
let slider1 = Slider1(frame: CGRectZero)
override func viewDidLoad() {
super.viewDidLoad()
label.textColor = UIColor.darkTextColor()
slider1.backgroundColor = UIColor.lightGrayColor()
view.addSubview(slider1)
}
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20.0
let width = view.bounds.width - 2 * margin
slider1.frame = CGRect(x: margin, y: 3 * margin, width: width, height: 1.5 * margin)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Here is my UIControl subclass:
class Slider1: UIControl {
var minimumValue = 0.0
var maximumValue = stepperValue.value
var value = 0
let trackLayer = CALayer()
var trackHeight:CGFloat = 2.0
var trackColor = UIColor.blackColor().CGColor
var tickHeight:CGFloat = 8.0
var tickWidth: CGFloat = 2.0
var tickColor = UIColor.blackColor().CGColor
let thumbLayer = CALayer()
var thumbColor = UIColor.blackColor().CGColor
var thumbMargin:CGFloat = 2.0
var thumbWidth: CGFloat {
return CGFloat(bounds.height)
}
}
If you want a slider, use a slider. You will have a LOT of work to do if you want to create your own slider-like control starting from UIControl.
Just make your view controller drive everything.
Have the value changed event on your stepper trigger an action in the view controller.
In that stepperValueChanged action, fetch the new value of the stepper and use it to change the maximum value of the slider. That's all there is to it.

Why custom delegate protocol contains an optional value - SWIFT

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
}

Resources