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

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.

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

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.

Changing variables of a custom UIView in another custom ViewController

I know it may be the basic question but I am new to Swift.
Also, I have tried various solutions on SO but could not resolve the issue.
So if anyone can help me with my problem.
I have a custom UIVIEW class as follows:
class SearchTextFieldView: UIView, UITextFieldDelegate{
public var searchText = UITextField()
override init(frame: CGRect) {
super.init(frame: frame)
initializeUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeUI()
}
func initializeUI() {
searchText.placeholder = "Enter model no"
searchText.backgroundColor = UIColor.white
searchText.textColor = UIColor.darkGray
searchText.layer.cornerRadius = 5.0
searchText.delegate=self
self.addSubview(searchText)
}
override func layoutSubviews() {
searchText.frame = CGRect(x: 20.0, y: 5.0, width: self.frame.size.width - 40,height : self.frame.size.height - 10)
}
}
Now I want to set text to SearchText textfield from another class which is as follows:
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
// Do any additional setup after loading the view.
}
func setupUI() {
let searchTextFieldView = SearchTextFieldView()
self.view.addSubview(searchTextFieldView) //adding view containing search box view at the top
**searchTextFieldView.searchText.text = "My Text"**
}
I am using Storyboard. Also, I can see the textfield with placeholder text.only problem is I can not set text to it.
Can anybody help. Whats wrong in my code.
It is needed to call searchTextFieldView.setNeedsDisplay(), this will in turn call override func draw(_ rect: CGRect) in class SearchTextFieldView.
Add override func draw(_ rect: CGRect) {} in SearchTextFieldView, and try setting searchText.text = <someValue> in draw(). You can use a String property in SearchTextFieldView, to get <someValue> from the client (one who is using SearchTextFieldView) class.
You are creating you view via SearchTextFieldView(), while you have 2 available initializers init(frame:) and init?(coder:).
If you change
let searchTextFieldView = SearchTextFieldView()
with
let searchTextFieldView = SearchTextFieldView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
you will see the text.
You are not setting frame to the view. Also you are not loading the .xib in the view class. It should be like:-
class SearchTextFieldView: UIView, UITextFieldDelegate{
//MARK:- Initializer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize(withFrame: self.bounds)
}
override init(frame : CGRect) {
super.init(frame: frame)
initialize(withFrame: frame)
}
//MARK: - View Initializers
func initialize(withFrame frame : CGRect) {
Bundle.main.loadNibNamed("SearchTextFieldView", owner: self, options: nil)
view.frame = frame
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(view)
initializeUI()
}
}
Now you can call the below code in view controller:-
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
// Do any additional setup after loading the view.
}
func setupUI() {
let searchTextFieldView = SearchTextFieldView(frame: ?*self.view.bounds)
self.view.addSubview(searchTextFieldView)
//adding view containing search box view at the top
searchTextFieldView.searchText.text = "My Text"
}
Don't forget to create an xib with name "SearchTextFieldView.xib" as you are loading that nib in your initialize function.
Hope it helps :)
add frame for the searchTextFieldView inside setupUI() method. because the View got loaded on the view but its doesn't have a frame (x,y position, width and height). Change your UIViewController's colour to grey and u can see the your view loaded on the left corner (0,0). set frame size for the view that will solve this problem.

Swift IOS transparent view over original view. Overlay. Subview

I can use the following code to override drawRect() in swift, which works fine.
override func drawRect(rect: CGRect) {
var path = UIBezierPath(ovalInRect: rect)
UIColor.greenColor().setFill()
path.fill()
}
I want to add subview(or second view) over the original view.
var overlayView = UIView()
self.view.addSubview(overlayView)
My question is, how can I override the drawRect() of a subview?
Or, how can I just make the subview a new view instead and place it over the original view?
Create a custom UIView subclass, e.g.
class MyCustomView: UIView {
override func drawRect(rect: CGRect) {
// do whatever you need to do
}
}
Then replace var overlayView = UIView() with var overlayView = MyCustomView()

Create a PopUp in Swift using a custom UIView

I am trying to create a custom UIView and display it as a pop up in my main View using Swift.
My Custom UIView code is
class DatePopUpView: UIView {
var uiView:UIView?
override init() {
super.init()
self.uiView = NSBundle.mainBundle().loadNibNamed("DatePopUpView", owner: self, options: nil)[0] as? UIView
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
required override init(frame: CGRect) {
super.init(frame: frame)
}
}
And I am Calling it in my main view as:
#IBAction func date_button_pressed (sender : AnyObject?) {
var popUpView = DatePopUpView()
var centre : CGPoint = CGPoint(x: self.view.center.x, y: self.view.center.y)
popUpView.center = centre
popUpView.layer.cornerRadius = 10.0
let trans = CGAffineTransformScale(popUpView.transform, 0.01, 0.01)
popUpView.transform = trans
self.view .addSubview(popUpView)
UIView .animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
popUpView.transform = CGAffineTransformScale(popUpView.transform, 100.0, 100.0)
}, completion: {
(value: Bool) in
})
}
But popUp is not Coming. I used breakpoint and noticed that value is getting assigned to my popUpView but still it is not displayed on my main View. Please Help
Please Note: I am using StoryBoard for my mainView and custom View i have made using xib.
Without additional description on what you are attempting to do, may I suggest something like the code below? Basically, you can use .hidden feature of a view (or any other control) to show/hide the view. You can set the size and positioning of the view to be popped by using the layout editor.
import UIKit
class ViewController: UIViewController {
var popped = false
var popupBtnTitle = "Show Popup"
#IBAction func popupButton(sender: UIButton) {
popped = !popped
anotherView.hidden = !popped
popupBtnTitle = popped ? "Hide Popup" : "Show Popup"
popupButtonOutlet.setTitle(popupBtnTitle, forState: UIControlState.Normal)
}
#IBOutlet weak var popupButtonOutlet: UIButton!
#IBOutlet weak var anotherView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
popped = false
anotherView.hidden = !popped
popupButtonOutlet.setTitle(popupBtnTitle, forState: UIControlState.Normal)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Resources