Changing class in a UIView (Swift) - ios

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

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.

A Expected Declaration Error when trying to assign a text to label

I'm trying to create a label class which contain other labels.
here is my code
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
class mainLabel: UILabel{
var top: UILabel! = UILabel()
top.text = "text" //*Expected declaration error
var down: UILabel! = UILabel()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
There are several issues with your code. The error you receive is because you can only declare variables or functions in a class scope and using top.text you're trying to modify an instance property of the class outside the function scope, which is not allowed.
Secondly, you shouldn't declare a class inside a function that rarely makes sense.
Lastly, don't declare anything as an implicitly unwrapped optional (UILabel!) if you're assigning a value to it right away.
There are several ways to create a reusable UI element that consists of 2 UILabel and can be created programatically. You can subclass a UIStackView to handle the layout automatically or if you want more control, you could simply subclass UIView, add the 2 UILabels as subViews and handle the layout by adding Autolayout constraints programatically.
Here's a solution using a UIStackView subclass. Modify any properties to fit your exact needs, this is just for demonstration.
class MainLabel: UIStackView {
let topLabel = UILabel()
let bottomLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
axis = .vertical
distribution = .fillEqually
addArrangedSubview(topLabel)
addArrangedSubview(bottomLabel)
topLabel.textColor = .orange
topLabel.backgroundColor = .white
bottomLabel.textColor = .orange
bottomLabel.backgroundColor = .white
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Test in a Playground:
PlaygroundPage.current.needsIndefiniteExecution = true
let mainLabel = MainLabel(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
PlaygroundPage.current.liveView = mainLabel
mainLabel.topLabel.text = "Top"
mainLabel.bottomLabel.text = "Bottom"

Using scroll view programmatically

I am trying to develop an app with the UI created only programmatically.
I want to create a simple view which is a UIScrollView (to be able to scroll the view when the keyboard is appearing) with a containerView (UIView) where we can find a button.
I am using PureLayout to make easier the set up of constraints, swift 4, Xcode 9.2 beta
Below the class of this view
class SimpleView: UIScrollView {
var containerView: UIView!
var signInButton: UIButton!
var signInLabel: UILabel!
var screenSize: CGSize = CGSize.zero
var shouldSetupConstraints = true
override init(frame: CGRect) {
super.init(frame: frame)
self.screenSize = frame.size
self.containerView = UIView(frame: CGRect.zero)
self.signInButton = UIButton(frame: CGRect.zero)
self.signInLabel = UILabel(frame: CGRect.zero)
self.addSubview(self.containerView)
self.containerView.addSubview(self.signInButton)
self.signInButton.addSubview(self.signInLabel)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func updateConstraints() {
if(shouldSetupConstraints) {
self.layoutSignInButton()
self.layoutSignInLabel()
shouldSetupConstraints = false
}
super.updateConstraints()
}
private func layoutContainerView() {
self.containerView.autoPinEdgesToSuperviewEdges()
self.containerView.backgroundColor = UIColor.yellow
}
private func layoutSignInButton() {
self.signInButton.autoPinEdge(toSuperviewEdge: .right)
self.signInButton.autoPinEdge(toSuperviewEdge: .left)
self.signInButton.autoPinEdge(toSuperviewEdge: .top)
self.signInButton.autoSetDimension(.height, toSize: 55.0)
self.signInButton.backgroundColor = UIColor(hex: "#FD9FA2")
}
private func layoutSignInLabel() {
self.signInLabel.autoPinEdgesToSuperviewEdges()
self.signInLabel.shadowColor = UIColor(hex: "#9A615E")
self.signInLabel.shadowOffset = CGSize(width: 0.0, height: 2)
self.signInLabel.text = NSLocalizedString("SIGN IN", comment: "")
self.signInLabel.textAlignment = .center
self.signInLabel.textColor = UIColor.white
self.signInLabel.font = UIFont.boldSystemFont(ofSize: 15.0)
self.signInLabel.backgroundColor = UIColor.clear
}
}
Below the code of the UIViewController subclass embedding the previous view
class SignInViewController: UIViewController {
var simpleView: SimpleView!
override func viewDidLoad() {
super.viewDidLoad()
self.simpleView = SimpleView(frame: self.view.bounds) // with SimpleView(frame: self.view.frame) has the same behaviour
self.view.addSubview(self.simpleView)
self.simpleView.autoPinEdgesToSuperviewEdges()
self.navigationController?.navigationBar.isHidden = true
}
}
Unfortunatly the result is not the one expected : see below
What am I missing ?
Different points are missing:
- The position of the button is weird (space between the button and the top / left side of the button partly hidden outside of the screen)
- the container view is invisible (backgroundColor = UIColor.yellow has no effect)
Thank you by advance !
//////////////////////////// EDIT ////////////////////////////////
Below a screenshot of the exact same code using a UIView instead of UIScrollView
Class SimpleView: UIView {
enter image description here
The content of a UIScrollView must also define the .contentSize` of the scroll view.
I don't use PureLayout, so I don't know what the syntax is, but in your layoutContainerView() func, you need to also do:
self.containerView ... set width dimension to SuperviewWdith
That will set the content of containerView to the width of the scroll view, and that should fix the width part.
I assume you will be adding elements to containerView and set their constraints to control the height of it.

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!

Resize a UIView inside a stackview

I need help to accomplish this:
Its a group of 3 photos. The first photo is the button screen before the click, the second photo is the screen after the click and the third photo is the way i've designed in the IB using stackview
I've being trying to create this and this is the result i've got so far. I still haven't created anything as the After button click image shows because of this:
My Result now
As you can see in the gif when i press the button, the UIView height constraint.constant is set to a higher value and the UIView get higher. All i want is the stripped background to get higher as well.
This is the way the view is disposed in IB.
And finally, this is the way i've coded
class LetterScreenViewController: UIViewController, ResizeWordViewDelegate {
#IBOutlet weak var youTubeView: YouTubeView!
#IBOutlet weak var phonemeView: PhonemeView!
#IBOutlet weak var wordView: WordView!
var letterPresenter: LetterPresenter?
#IBOutlet weak var heightWordViewConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
delegateWordObj()
if let presenter = letterPresenter {
presenter.setupView()
}
}
#IBAction func dismissLetter(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
func delegateWordObj() {
wordView.resizeWordViewDelegate = self
}
func didPressButton() {
if heightWordViewConstraint.constant <= 0 {
heightWordViewConstraint.constant = 30
}else{
heightWordViewConstraint.constant = 0
}
}
}
import UIKit
protocol ResizeWordViewDelegate {
func didPressButton()
}
class WordView: UIView {
#IBOutlet weak var backgroundListras: UIImageView!
#IBOutlet var WordView: UIView!
#IBOutlet weak var showWordButton: UIButton!
var resizeWordViewDelegate: ResizeWordViewDelegate!
override init(frame: CGRect) {
super.init(frame: frame)
xibInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibInit()
}
func xibInit() {
Bundle.main.loadNibNamed("WordView", owner: self, options: nil)
addSubview(WordView)
WordView.frame = self.bounds
WordView.round(corners: [.topLeft,.topRight], radius: 120)
WordView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
#IBAction func showWordButtonAction(_ sender: Any) {
resizeWordViewDelegate.didPressButton()
self.frame.size = CGSize(width: 375, height: 200)
self.backgroundListras.frame.size = CGSize(width: 375, height: 200)
}
}
I want to know a way to resize this UIView with all the content in it. After finding a solution to increase the height i'll be able to put all the others components shown when i press the button.
I believe that your goal is to create something like this:
All I had to do was change the height of the constraint programmatically
Check the solution in the project below:
https://gitlab.com/DanielLimaDF/ResizeTest.git
Important: Beware of addSubview, it can remove constraints from a View if the constraints were created before calling addSubview

Resources