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

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.

Related

setNeedsDisplay() is not calling draw(_ rect: CGRect)

I found similar questions in this website but none of them solved my issue. Please carefully read the whole code. The setNeedsDisplay() function is not calling the draw(_ rect: CGRect) when I want to draw a line on MyView.
I created a view called "myView" in storyboard as sub View of "MyViewController" and created a IBOutlet to the view controller.
Then I created a class called "MyViewClass" and set it as the class of "myView" in storyboard.
I set the bool value drawLine to true and call function updateLine(),
the problem is the setNeedsDispaly() is not triggering the draw(_ rect: CGRect) function.
import UIKit
class MyViewClass : UIView{
var drawLine = Bool() // decides whether to draw the line
func updateLine(){
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
if drawLine == true{
guard let context = UIGraphicsGetCurrentContext() else{
return
}
context.setLineWidth(4.0)
context.setStrokeColor(UIColor.red.cgColor)
context.move(to: CGPoint(x: 415 , y: 650))
context.addLine(to: CGPoint(x:415 , y: 550))
context.strokePath()
}
}
}
class myViewController:UIViewController {
#IBOutlet weak var insideView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let myViewInstance = MyViewClass()
myViewInstance.drawLine = true
myViewInstance.updateLine()
}
}
I'm fairly new to Swift. Any help will appreciated. Thanks.
You have 2 issues:
you're not giving the new view a frame so it defaults to zero width and zero height
you're not adding the view to the view heirarchy
let myViewInstance = MyViewClass(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height))
myViewInstance.backgroundColor = .white // you might want this a well
myViewInstance.drawLine = true
self.view.addSubview(myViewInstance)
myViewInstance.updateLine()
A cleaner way to have your view redraw when a property (such as drawLine) is changed is to use a property observer:
var drawLine: Bool = true {
didSet {
setNeedsDisplay()
}
}
That way, the view will automatically redraw when you change a property and then you don't need updateLine().
Note that setNeedsDisplay just schedules the view for redrawing. If you set this on every property, the view will only redraw once even if multiple properties are changed.
Note: viewDidLoad() would be a more appropriate place to add your line because it is only called once when the view is created. viewWillAppear() is called anytime the view appears so it can be called multiple times (for instance in a UITabView, viewWillAppear() is called every time the user switches to that tab).
MyView class
class myView : UIView {
var updateView : Bool = false {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
print("in Draw")
}
}
MyViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let view = myView()
view.updateView = true
}
}
In the storyboard i had setup the view class as myView therefore its visible.
Hopefully this will help you.

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!

How to access views created in viewDivLoad

class ViewController: UIViewController
{
#IBAction func button_down(_ sender: Any)
{
do_something(view: myView);
}
override func viewDidLoad()
{
super.viewDidLoad();
let screen_size: CGRect = UIScreen.main.bounds;
var myView = UIView(frame: CGRect(x: 0, y: screen_size.height/2 - screen_size.width/2, width: screen_size.width, height: screen_size.width));
myView.backgroundColor = UIColor.red;
self.view.addSubview(myView);
}
}
When I try to compile this code, I get an error:
Use of unresolved identifier 'myView'
How can I solve this problem? I have tried declaring myView outside of viewDivLoad, but that's not permitted.
You should definitely do some research on the concept of "scope" in computer programming - it'll be difficult to go too far without it. myView only exists within the scope of viewDidLoad. If you want it to be available outside of that, you can make it a property of the class like so:
class ViewController: UIViewController
{
var myView: UIView!
#IBAction func buttonDown(_ sender: Any)
{
doSomething(view: myView)
}
override func viewDidLoad()
{
super.viewDidLoad()
let screenSize: CGRect = UIScreen.main.bounds
myView = UIView(frame: CGRect(x: 0, y: screenSize.height/2 - screenSize.width/2, width: screenSize.width, height: screenSize.width))
myView.backgroundColor = UIColor.red
self.view.addSubview(myView)
}
}
A couple of other notes:
You don't need semicolons at the end of lines in Swift.
It seems you're using Interface Builder, so I would suggest creating myView that way instead of programmatically.
Function and variable names in Swift should be camel cased - so buttonDown instead of button_down, screenSize instead of screen_size.

IOS Swift3 how to Remove Custom UIView randomly

I am building an iOS Application in swift 3, where I am creating dynamic UIViews. I need to remove custom view randomly. Please help me I am stuck with this for a long time. Thanks In Advance
class ViewController: UIViewController {
var myView: subView!
var y : CGFloat!
#IBOutlet weak var addButton: UIButton!
override func viewDidLoad() {
y = 1
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func cancelbutton(_ sender: UIButton) {
myView.removeFromSuperview()
}
#IBAction func buttonAction(_ sender: Any) {
y = y + 110
myView = subView(frame: CGRect(x: 80, y: y, width: 300, height: 100))
myView.backgroundColor = UIColor.green
myView.actionButton.addTarget(self, action: (#selector(cancelbutton(_:))), for: UIControlEvents.touchUpInside)
self.view.addSubview(myView)
}
}
As you can see in the above image when i click on Close the SubView(custome View) closes, but where as MyView with green color does not go and stays there. Someone please Help.........
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("subView",owner: self, options:nil)
self.addSubview(self.views)
Bundle.main.loadNibNamed("subView",owner: self, options:nil)
self.addSubview(self.views)
}
override init(frame: CGRect)
{
super.init(frame: frame)
Bundle.main.loadNibNamed("subView", owner: self, options: nil)
views.frame = bounds
self.addSubview(self.views)
}
#IBAction func buttonAction(_ sender: Any) {
views.removeFromSuperview()
}
The thing you are doing wrong is that you add multiple views of subView that why you selected does not remove. Please modify your code like given below.
The thing I have done is that whenever you will add new subView you will also set its tag value and when you select view to remove its tag value and place an if statement on that tag value.
class ViewController: UIViewController {
var myView: subView!
var y : CGFloat!
var tag : Int = 0
#IBOutlet weak var addButton: UIButton!
override func viewDidLoad() {
y = 1
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func cancelbutton(_ sender: UIButton)
{
let selectViewTagValue : Int = sender.tag /// save the selected view tag value
for object in self.view.subviews {
if ((object is subView) && object.tag == selectViewTagValue)
{
object.removeFromSuperview()
}
}
}
#IBAction func buttonAction(_ sender: Any) {
y = y + 110
myView = subView(frame: CGRect(x: 80, y: y, width: 300, height: 100))
myView.tag = tag
myView.actionButton.tag = tag
tag = tag + 1
myView.backgroundColor = UIColor.green
myView.actionButton.addTarget(self, action: (#selector(cancelbutton(_:))), for: UIControlEvents.touchUpInside)
self.view.addSubview(myView)
}
Try to change cancel action like this:
func cancelbutton(_ sender: UIButton)
{
if let myView = sender.superview {
myView.removeFromSuperview()
}
}
Just remove the button's container view, not the global myView.
I managed to fixed the delete issue, but currently I am unable to relocate the positions the customs views, as shows in the picture below, Kindly help me with this.

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.

Resources