How to arrange a view in architecture MVC? When coding through code - swift

I am studying architecture MVC and I write the interface through code.
I am trying to link a VIEW and a CONTROLLER.
But it doesn't work. What's my mistake?
Controller:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
ViewExample.setupUI()
}
}
View:
import UIKit
class ViewExample: UIView {
static func setupUI() {
let view = UIView()
let labelTitle: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello, world!"
return label
}()
view.backgroundColor = .white
view.addSubview(labelTitle)
NSLayoutConstraint.activate([
labelTitle.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
labelTitle.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
labelTitle.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I understand why a static is needed. Why doesn't it work for me? I have not found any good example on github. Therefore, I have to turn to you

I don't see why you would use static, you want to set up the UI of a UIView's instance, thus no need to have a static class method.
In your ViewExample:
import UIKit
class ViewExample: UIView {
func setupUI() {
let labelTitle: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello, world!"
return label
}()
self.backgroundColor = .white
self.addSubview(labelTitle)
NSLayoutConstraint.activate([
labelTitle.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 10),
labelTitle.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10),
labelTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10),
])
}
override init(frame: CGRect){
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
}
In your ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let view = ViewExample(frame: (x: 0, y: 0, width: 200, height: 50)) //Change the frame according to where you want to position your view
self.addSubview(view)
}
}

Related

Unable to simultaneously satisfy constraints of InputAccessoryView

I have an InputAccessoryView with a single button in. However, I am getting the following output.
I have set up my InputAccessoryView in my ViewController as below;
lazy var customInputView: ButtonInputView = {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let view = ButtonInputView(frame: frame)
view.doneButton.addTarget(self, action: #selector(signIn), for: .touchUpInside)
return view
}()
override var inputAccessoryView: UIView? {
return customInputView
}
Xcode is breaking the height constraint of 50 set in the frame of my view. Any reason why??
CUSTOM ACCESSORY INPUT VIEW CLASS
class ButtonInputView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var doneButton: UIButton = {
let view = UIButton()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = Color.accent
view.setTitle("Done", for: .normal)
view.titleLabel?.font = Font.mainButton
view.layer.cornerRadius = 19
return view
}()
let separator: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .opaqueSeparator
return view
}()
private func setUpView() {
backgroundColor = Color.background
addSubview(doneButton)
addSubview(separator)
let separatorHeight = (1 / (UIScreen.main.scale))
if let titleLabelText = doneButton.titleLabel?.text {
doneButton.widthAnchor.constraint(equalToConstant: titleLabelText.width(usingFont: Font.mainButton) + 32).isActive = true
}
NSLayoutConstraint.activate([
separator.topAnchor.constraint(equalTo: topAnchor),
separator.leadingAnchor.constraint(equalTo: leadingAnchor),
separator.trailingAnchor.constraint(equalTo: trailingAnchor),
separator.heightAnchor.constraint(equalToConstant: separatorHeight),
doneButton.topAnchor.constraint(equalTo: separator.bottomAnchor, constant: 6),
doneButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
doneButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -6),
])
}
override var intrinsicContentSize: CGSize {
return CGSize.zero
}
override func didMoveToWindow() {
super.didMoveToWindow()
if #available(iOS 11.0, *) {
if let window = window {
bottomAnchor.constraint(lessThanOrEqualToSystemSpacingBelow: window.safeAreaLayoutGuide.bottomAnchor,
multiplier: 1.0).isActive = true
}
}
}
}

Is there a way to update views in StackView without removing the view and re adding it?

I have views which use autolayout. I know the exact width I want each view to be (each width is different). The StackView is nested inside of a collection view. The views inside the stack view are loaded dynamically from the main view controller. I want to update the stack views views when the collection view reloads without removing the views and adding them again. I cannot remove the views and re add them because some of the views have textfields which will dismiss the keyboard if reloaded. I have explained the code below
Thank you for any help.
EDIT
This is the layout I am looking for.
I use Carbon for rendering the views into the collection view:
private func render() {
renderer.render {
Section(
id: "testing row collection",
cells: {
Row(id: "row id", data: [
Input(
id: 6,
props: InputContent.State(
text: self.state.inputText,
placeholder: "Search",
size: .medium
),
onChange: { [weak self] text in
print("text is:", text)
self?.state.inputText = text
}
)
])
}
)
}
}
NOTE: the image and code do not match implementation. The image shows 3 views, the code only shows 1.
The class Row is the class that renders the view that holds the UIStackView. Inside that I have a list of views, at the moment only 1 view. It's sets text to be self.state.inputText which is a variable in the view controller. Then on any change in the textfield I set self.state.inputText to be that text. When the variable state is set, render() is run.
The Row class:
This class renders the actual UIView which is called RowContent. It calls the updateState(state:) method shown in the RowContent class. This is called when the collection view renders
The RowContent class:
class RowContent: UIView {
var stack: UIStackView
var state: State
struct State {
var views: [UIView] = [UIView]()
}
public func updateState(_ state: State? = nil) {
self.state = state ?? self.state
//updateView()
}
required init(state: State) {
self.state = state
self.stack = UIStackView(arrangedSubviews: state.views)
super.init(frame: .zero)
self.setupStackView()
}
...
}
The stack view gets populated in the initialiser with the views, then I add the constraints in setupStackview()
At the moment, the views never update when the collection view re renders, meaning that the text property in Input never gets updated. I want to update the views when the collection view re renders. This would be in the updateView() method which is commented out. I do not know how to update the views in the collectionView without removing the views and re adding them.
Thanks.
I have added an example here: https://github.com/4ndrewHarri5/ExampleRow
Give this a try...
We start with a Tag "base" view class - it will set the background color and rounded corners for the Tag views:
// background and rounded corners
class TagBaseView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
layer.cornerRadius = 12
layer.masksToBounds = true
backgroundColor = UIColor(red: 0.96, green: 0.95, blue: 0.97, alpha: 1.0)
}
}
Subclass TagBaseView, adding a UILabel:
class TagLabelView: TagBaseView {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func commonInit() -> Void {
super.commonInit()
label.font = .boldSystemFont(ofSize: 18.0)
label.numberOfLines = 0
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
])
}
}
Subclass TagBaseView, adding a UITextField:
class TagFieldView: TagBaseView {
let field = UITextField()
var textChangeCallback: ((String)->())?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func commonInit() -> Void {
super.commonInit()
field.font = .boldSystemFont(ofSize: 18.0)
addSubview(field)
field.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain text field Leading / Trailing
field.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
field.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
// center it vertically
field.centerYAnchor.constraint(equalTo: centerYAnchor),
// require at least 8-pts Top and Bottom
field.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 8.0),
field.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
])
field.addTarget(self, action: #selector(self.textDidChange(_:)), for: .editingChanged)
}
#objc func textDidChange(_ textField: UITextField) -> Void {
guard let s = textField.text else {
return
}
// tell the controller the text changed
textChangeCallback?(s)
}
}
View controller class - creates a horizontal stack view, with a TagLabelView "left" view, a TagLabelView "center" view, and a TagFieldView "right" view.
When we edit the text field in the "right" view, it will use a closure to "call back" to the controller where we handle the text change and update the left and center views:
class EditStackViewController: UIViewController {
let stack: UIStackView = {
let v = UIStackView()
v.spacing = 8
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let leftView = TagLabelView()
let centerView = TagLabelView()
let rightView = TagFieldView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(stack)
stack.addArrangedSubview(leftView)
stack.addArrangedSubview(centerView)
stack.addArrangedSubview(rightView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain stack view Top / Leading / Trailing
// (no Bottom or Height)
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
// make "center" view 45% of the width
centerView.widthAnchor.constraint(equalTo: stack.widthAnchor, multiplier: 0.45),
// make "right" view equal width to "left" view
rightView.widthAnchor.constraint(equalTo: leftView.widthAnchor),
])
rightView.field.placeholder = "Search"
// TagSearchView tells us when the field is being edited
rightView.textChangeCallback = { [weak self] str in
guard let self = self else {
return
}
self.leftView.label.text = str
self.centerView.label.text = str
}
}
}
Results:

Change view with Segment View Control

I have created a view in my View controller which has UISegmentControl and a UIScrollView
let segmentControl : UISegmentedControl = {
let segmentItems = ["Personal","Statistics","Calendar"]
let segmentControl = UISegmentedControl(items: segmentItems)
segmentControl.selectedSegmentIndex = 0
segmentControl.addTarget(self, action: #selector(selectIn(sender:)), for: .valueChanged)
segmentControl.translatesAutoresizingMaskIntoConstraints = false
return segmentControl
}()
let subView : UIScrollView = {
let view = UIScrollView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
In viewDidLoad I added
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.isHidden = true
view.addSubview(segmentControl)
view.addSubview(subView)
setLayout()
}
and added layout as follows
func setLayout(){
NSLayoutConstraint.activate([
segmentControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
segmentControl.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
segmentControl.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
segmentControl.heightAnchor.constraint(equalToConstant: 30),
subView.topAnchor.constraint(equalTo: segmentControl.bottomAnchor, constant: 10),
subView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
subView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
subView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
and tried to add PersonalView (UIView) when any segmentControl is pressed
#objc func selectIn(sender: UISegmentedControl){
subView.addSubview(pvc)
}
My personalView is as follows
class PersonalView: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UITableViewDelegate, UITableViewDataSource {
let tempValueForTable : Int = 10
let todayLabel : UILabel = {
let label = UILabel()
label.text = "Today"
label.font = .montserratSemiBold
label.textAlignment = .center
label.backgroundColor = .green
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(todayLabel)
setLayout()
}
private func setLayout(){
NSLayoutConstraint.activate([
todayLabel.topAnchor.constraint(equalTo: self.topAnchor),
todayLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
todayLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),
todayLabel.heightAnchor.constraint(equalToConstant: 25),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}}
Still I am not able to get view and in further update I need to add more view in same ScrollView
1.when you are using auto layout constraints you must set this false after add.
SomeView.translatesAutoresizingMaskIntoConstraints = false
2.After you add your subView you must set its constraints and call view.layoutIfNeeded()
subView.addSubview(pvc)
pvc.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
pvc.trailingAnchor.constraint(equalTo: subView.trailingAnchor),
pvc.leadingAnchor.constraint(equalTo: subView.leadingAnchor),
pvc.topAnchor.constraint(equalTo: subView.topAnchor),
pvc.bottomAnchor.constraint(equalTo: subView.bottomAnchor),
])
view.layoutIfNeeded()

Using Auto Layout to align a custom UIView to the view programatically

I am trying to align a custom UIView to the center of view using auto layout but I am getting a blank screen.
Please can someone advise on where my code is incorrect?
My Controller's code:
import UIKit
class TestViewController: UIViewController{
var margin: UILayoutGuide!
override func viewDidLoad() {
super.viewDidLoad()
margin = self.view.layoutMarginsGuide
let size = CGRect.init(x: 0, y: 0, width: 200, height: 100)
let sq1 = Square.init(frame: size, color: UIColor.blue)
view.addSubview(sq1)
sq1.translatesAutoresizingMaskIntoConstraints = false
sq1.centerXAnchor.constraint(equalTo: margin.centerXAnchor).isActive = true
sq1.topAnchor.constraint(greaterThanOrEqualTo: margin.topAnchor, constant: 20).isActive = true
sq1.leadingAnchor.constraint(greaterThanOrEqualTo: margin.leadingAnchor, constant: 20).isActive = true
sq1.trailingAnchor.constraint(greaterThanOrEqualTo: margin.trailingAnchor, constant: 20).isActive = true
}
}
My Custom View's code:
class Square: UIView{
init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
makeSquare(frame: frame, colour: color)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func makeSquare(frame: CGRect, colour: UIColor){
self.layer.frame = self.bounds
self.layer.backgroundColor = colour.cgColor
}
}
You need a height constraint , also don't mix frame layout with auto layout you should use either of them
sq1.heightAnchor.constraint(equalToConstant:200).isActive = true
Also if you set leading and trailing
sq1.leadingAnchor.constraint(greaterThanOrEqualTo: margin.leadingAnchor, constant: 20).isActive = true
sq1.trailingAnchor.constraint(greaterThanOrEqualTo: margin.trailingAnchor, constant: 20).isActive = true
then no need for a centerX
sq1.centerXAnchor.constraint(equalTo: margin.centerXAnchor).isActive = true
NSLayoutConstraint.activate([
sq1.topAnchor.constraint(equalTo: margin.topAnchor, constant: 20),
sq1.leadingAnchor.constraint(equalTo: margin.leadingAnchor, constant: 20),
sq1.trailingAnchor.constraint(equalTo: margin.trailingAnchor, constant: -20),
sq1.heightAnchor.constraint(equalToConstant: 200)
])
After some small additions:
class Square: UIView {
init(color: UIColor) {
// The view is ignored with auto layout.
super.init(frame: .zero)
backgroundColor = color
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then in the viewDidLoad function:
override func viewDidLoad() {
super.viewDidLoad()
let sq = Square(color: .orange)
sq.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(sq)
sq.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
sq.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// -40 to compensate for the leading and trailing.
sq.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1, constant: -40).isActive = true
sq.heightAnchor.constraint(equalTo: sq.widthAnchor).isActive = true
// Do any additional setup after loading the view.
}
I've taken the freedom to set the height equal to the width, as you create a square, but not sure if you want that. Hope this helps!
try it
class Square: UIView {
func setColor(colour: UIColor){
self.backgroundColor = colour
}
}
call this func in ViewDidLoad
func initView() {
self.view.backgroundColor = .red
margin = self.view.layoutMarginsGuide
let sq1 = Square()
view.addSubview(sq1)
sq1.translatesAutoresizingMaskIntoConstraints = false
sq1.centerXAnchor.constraint(equalTo: margin.centerXAnchor).isActive = true
sq1.topAnchor.constraint(greaterThanOrEqualTo: margin.topAnchor, constant: 20).isActive = true
sq1.widthAnchor.constraint(equalToConstant: 200).isActive = true
sq1.heightAnchor.constraint(equalToConstant: 200).isActive = true
sq1.setColor( colour: UIColor.blue)
}
result

creating custom UITextfield

how do i make custom UITextfield? i have created class with uitextfield as a subclass then i added label it shows up, but the border(UIView) are not showing up here is the code
import UIKit
class borderedTextField: UITextField{
let viewz:UIView! = UIView()
var border:UIView! = UIView()
var name:UILabel! = UILabel()
override func draw(_ rect: CGRect) {
self.addSubview(viewz)
}
override func layoutSubviews() {
backgroundColor = UIColor.green
}
func placement(){
border.frame.size.height = 15
border.backgroundColor = UIColor.black
border.translatesAutoresizingMaskIntoConstraints = false
viewz.addSubview(border)
border.heightAnchor.constraint(equalToConstant: 15).isActive = true
border.widthAnchor.constraint(equalToConstant: 100)
border.leftAnchor.constraint(equalTo: viewz.leftAnchor, constant: 10).isActive = true
border.bottomAnchor.constraint(equalTo: viewz.bottomAnchor, constant: -5).isActive = true
name.text = "whatt"
name.textAlignment = .left
name.textColor = UIColor.black
name.translatesAutoresizingMaskIntoConstraints = false
viewz.addSubview(name)
name.widthAnchor.constraint(equalToConstant: 100)
name.heightAnchor.constraint(equalToConstant: 50)
name.leftAnchor.constraint(equalTo: viewz.leftAnchor, constant: 5).isActive = true
name.topAnchor.constraint(equalTo: viewz.topAnchor, constant: 10).isActive = true
}
}
and i called it programatically (without storyboard) please let me know how to solve this problem without using storyboard i need it to be full code.
import UIKit
class signViewController: UIViewController,UITextFieldDelegate {
var animText:borderedTextField!
override func viewDidLoad() {
super.viewDidLoad()
let animText = borderedTextField()
animText.delegate = self
animText.placeholder = "click here"
animText.placement()
animText.frame = CGRect(x: 100, y: 500, width: 200, height: 90)
view.addSubview(animText)
}
}
You have to add init
override init (frame : CGRect) {
super.init(frame : frame)
self.placement()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.placement()
}
Also width of border is not active
border.widthAnchor.constraint(equalToConstant: 100).active = true
for name
name.widthAnchor.constraint(equalToConstant: 100).active = true
name.heightAnchor.constraint(equalToConstant: 50).active = true
Edit
border.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 10).isActive = true
border.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -5).isActive = true