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.
Related
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()
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.
I have implemented reusable button component using combination of .xib and cocoa class following this guide
It works, but there is an issue. In order to use it in one of my main View Storyboards I have to first drag in a normal view (referenced as superview in question title) and then apply my Button class, to make it a button.
This works, but initial view height and width alongside its white background persist, so I have to always manually rewrite those when I use my component, which in itself results in poor reusability.
Ideally, I'd like to drag in a view, set it to Button class and thats it, that view should instantly take buttons height and width and have transparent background. Is something like this achievable?
To light more context on this issue here are few useful bits of my implementation
1. Reusable component made as a .xib view and its own cocoa class
2. Contents of Button.swift
import UIKit
#IBDesignable class Button: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var label: UILabel!
#IBInspectable var buttonLabel: String? {
get {
return label.text
}
set(buttonLabel) {
label.text = buttonLabel
}
}
override init(frame: CGRect) {
super.init(frame: frame)
componentInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
componentInit()
}
private func componentInit() {
let bundle = Bundle(for: Button.self)
bundle.loadNibNamed("Button", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
}
3. Example usage (inside one of my main view storyboards) demonstrating how ordinary view is turned into a button, but has issues with height, width and background color
P.S ^ if its hard to tell what is going on in a gif above, I basically drag UIView into a story board and give it custom class attribute of Button, this turns that view into a button.
EDIT: Just to make it clearer, my question is: Can I apply width, height and transparent colour to my XIB's parent / super view? End goal here is to just drag in a view onto storyboard, give it custom class of a Button and thats it, it should be sized properly and have transparent background, as opposed to how it is at the moment (view doesn't get sized as button and has white background)
You have to pin your subviews in Button properly and also in Main.storyboard. Then your custom view will autosize. And clear color is working.
import UIKit
#IBDesignable
class Button: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var label: UILabel!
#IBInspectable
var backColor: UIColor = .clear {
didSet {
backgroundColor = backColor
contentView.backgroundColor = backColor
}
}
override var backgroundColor: UIColor? {
get {
return backgroundColor
} set {
}
}
#IBInspectable
var buttonLabel: String? {
get {
return label.text
}
set(buttonLabel) {
label.text = buttonLabel
}
}
override init(frame: CGRect) {
super.init(frame: frame)
componentInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
componentInit()
}
private func componentInit() {
let bundle = Bundle(for: Button.self)
bundle.loadNibNamed("Button", owner: self, options: nil)
addSubview(contentView)
contentView.frame = bounds
backgroundColor = backColor
contentView.backgroundColor = backColor
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
// for static height
contentView.heightAnchor.constraint(equalToConstant: 70).isActive = true
}
}
To ensure you CustomView would size itself properly, you can use bottom constraint >= 0. After testing reset to equals.
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"
I'm currently trying to learn constraints and styling programmatically in Swift. I'm also trying to maintain clean and modularized code by splitting up code that relates to "styling".
I simply have my LoginViewController:
import UIKit
class LoginViewController: UIViewController {
var loginView: LoginView!
override func viewDidLoad() {
super.viewDidLoad()
loginView = LoginView(frame: CGRect.zero)
self.view.addSubview(loginView)
// AutoLayout
loginView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets.zero, excludingEdge: .bottom)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
Then my LoginView:
import UIKit
class LoginView: UIView {
var shouldSetupConstraints = true
var headerContainerView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
// Header Container View
headerContainerView = UIView(frame: CGRect.zero)
headerContainerView.backgroundColor = UIColor(red:0.42, green:0.56, blue:0.14, alpha:1.0) // #6B8E23
headerContainerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(headerContainerView)
headerContainerView.topAnchor.constraint(equalTo: self.superview!.topAnchor)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func updateConstraints() {
if(shouldSetupConstraints) {
// AutoLayout constraints
shouldSetupConstraints = false
}
super.updateConstraints()
}
}
Where I am getting stuck is with just simply trying to add this headerContainerView to the top of my superview. I want to be able to add it so it pins itself to the top, left and right of the superview and only 1/3 of the superview's height. I continue to try and reference the superview with no success and I cannot find a solution that helps me understand on the internet. Any suggestions on how I can complete this?
Thank you for taking the time for those that respond.
NOTE: I did start out using PureLayout which is really nice. However, I am an individual that likes to understand what is going on behind the scenes or at least how to write the code at its base level. You can see that I am using a PureLayout function in my LoginViewController, but I am looking to change that. I would prefer a solution that doesn't add a third party library.
Here self in the custom UIView class is the parent view of headerContainerView so , You can add this , Also I recommend to learn constraints first without 3rd party libraries to fully understand the concept as you will learn a lot from seeing conflicts and other things , once done , shift to libraries
override init(frame: CGRect) {
super.init(frame: frame)
// Header Container View
headerContainerView = UIView(frame: CGRect.zero)
headerContainerView.backgroundColor = UIColor(red:0.42, green:0.56, blue:0.14, alpha:1.0) // #6B8E23
headerContainerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(headerContainerView)
headerContainerView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
headerContainerView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
headerContainerView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
headerContainerView.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier:1.0/3.0, constant: 0.0).active = true
}
// loginView layout
override func viewDidLoad() {
super.viewDidLoad()
loginView = LoginView(frame: CGRect.zero)
self.view.addSubview(loginView)
loginView.translatesAutoresizingMaskIntoConstraints = false
loginView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
loginView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
loginView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
loginView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
headerContainerView.snp.makeConstraints { (make) in
make.top.equalTo(self)
make.leading.and.trailing.equalTo(self)
make.height.equalTo(self.frame.height/3)
}
With SnapKit.
With SnapKit, you can do the following:
override func viewDidLoad() {
super.viewDidLoad()
loginView = LoginView(frame: CGRect.zero)
self.view.addSubview(loginView)
// AutoLayout
loginView.snp.makeConstraints { (make) in
make.left.equalTo(view.snp.left)
make.right.equalTo(view.snp.right)
make.top.equalTo(view.snp.top)
make.height.equalTo(view.snp.height).multipliedBy(1/3)
}
}