Proper File structure in MVC, while making UI programmatically - ios

I was wondering how to store in proper way Views, and all elements in iOS Projects, when I'm creating UI programmatically.
I have for ex. RegisterButton function, and this ViewController file.
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(loginRegisterButton)
setupLoginRegisterButon()
}
let loginRegisterButton: UIButton = {
let button = UIButton()
button.backgroundColor = UIColor(red: 120, green: 80, blue: 195)
button.setTitle("Register", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.white, for: .normal)
return button
}()
func setupLoginRegisterButon() {
loginRegisterButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginRegisterButton.topAnchor.constraint(equalTo: inputsContrainerView.bottomAnchor, constant: 12).isActive = true
loginRegisterButton.widthAnchor.constraint(equalTo: inputsContrainerView.widthAnchor).isActive = true
loginRegisterButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
I should create separate file in "Views", but how to do it in proper, and how to call function that creates all objects in proper way.
Thanks!

Related

Create a custom box with an arrow underneath using swift programatic ui

I am a junior swiftui developer, i am working on a project that require me to use UIKit in some of its part.
i want to achieve a view similar to the attached image which is an example of Waze Advertisment box:
after checking MapBox API and looking at some of the example i learned that if i want to achieve this i should use swift programatic ui which i am not that familiar with, in the example they have provided the following view:
and the way they have created it was as follow:
final class AdvertismentAnnotationView: UIView {
weak var delegate: AdvertismentAnnotationViewDelegate?
//Passing this model to get the data and display it
var advertismentModel: AdvertismentModel
var selected: Bool = false {
didSet {
selectButton.setTitle(selected ? "Deselect" : "Select", for: .normal)
}
}
var title: String? {
get { centerLabel.text }
set { centerLabel.text = newValue }
}
lazy var centerLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 10)
label.numberOfLines = 0
return label
}()
lazy var closeButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.black, for: .normal)
button.setTitle("X", for: .normal)
return button
}()
lazy var selectButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = #colorLiteral(red: 0, green: 0.4784313725, blue: 0.9882352941, alpha: 1)
button.layer.cornerRadius = 8
button.clipsToBounds = true
button.setTitle("Select", for: .normal)
return button
}()
//MARK: This is what i have added
//and image that will later fetch the image from advertisment.imageURL
lazy var adImage: UIImage = {
return UIImage()
}()
//MARK: Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .green
closeButton.addTarget(self, action: #selector(closePressed(sender:)), for: .touchUpInside)
selectButton.addTarget(self, action: #selector(selectPressed(sender:)), for: .touchUpInside)
[centerLabel, closeButton, selectButton].forEach { item in
item.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(item)
}
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: topAnchor, constant: 4),
closeButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -4),
centerLabel.topAnchor.constraint(equalTo: topAnchor, constant: 4),
centerLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -4),
centerLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 4),
selectButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
selectButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -4),
selectButton.leftAnchor.constraint(equalTo: leftAnchor, constant: 4)
])
}
}
what i want to achieve is a white rounded rectangle with an arrow underneath in its center and an image inside the box.
I have tried using swiftui in this uikit however i failed many time.
if this was a regular swiftui i would have created a ZStack with a rounded rectangle and a custom shape as of the arrow.
and using kingfisher to fetch the image.

Changing color of uibutton

I am trying to create a UIbutton that changes color to green when tapped and blue when deselected. My logic works however when I exit the screen it still shows blue when selected because it's the default color shown before the button is tapped. What can I do to resolve this ?
func setUpSelectDealerButton(){
selectDealerButton.layer.cornerRadius = 5
view.addSubview(selectDealerButton)
selectDealerButton.setTitle( "Select Dealer" , for: .normal)
selectDealerButton.backgroundColor = .systemBlue
selectDealerButton.snp.makeConstraints{ (make) in
make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-16)
make.top.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(16)
make.leading.equalTo(view.safeAreaLayoutGuide.snp.leading).offset(3)
make.trailing.equalTo(view.safeAreaLayoutGuide.snp.trailing).offset(-3)
make.height.equalTo(50)
make.width.equalTo(50)
}
selectDealerButton.addTarget(self, action: #selector(addToSelectedAccounts), for: .touchUpInside)
}
#objc func addToSelectedAccounts() {
if let selectedAccounts = delegate?.selectedAccounts, !selectedAccounts.contains(viewModel.account) {
delegate?.didSelect(account: viewModel.account)
}
if selectDealerButton.isSelected{
selectDealerButton.isSelected = false
selectDealerButton.setTitle( "Select Dealer" , for: .normal)
selectDealerButton.backgroundColor = .systemBlue
}
else{
selectDealerButton.isSelected = true
selectDealerButton.setTitle( "Dealer Selected" , for: .normal)
selectDealerButton.backgroundColor = .systemGreen
GlobalMBProgressHud.sharedInstance.showGlobalHud(withSuccess: true, text: "Selected!")
}
}
This is the screenshot: https://postimg.cc/xJt8ngy9
If I understand well this is an example of how to control button state, declare your objects:
let myButton: UIButton = {
let button = UIButton(type: .system)
button.layer.cornerRadius = 5
button.clipsToBounds = true
button.setTitleColor(.white, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let myLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.textAlignment = .center
label.font = .systemFont(ofSize: 20, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var buttonState = Int() // set to integer
in viewDidLoad add target to your button set constraints (how you prefer) and call handleChangeButton function:
view.backgroundColor = .red
let control = UserDefaults.standard.integer(forKey: "buttonState")
if control == 0 {
buttonState = 0
} else {
buttonState = control
}
myButton.addTarget(self, action: #selector(handleChangeButton), for: .touchUpInside)
view.addSubview(myButton)
myButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
myButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
myButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
myButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
view.addSubview(myLabel)
myLabel.leadingAnchor.constraint(equalTo: myButton.leadingAnchor).isActive = true
myLabel.trailingAnchor.constraint(equalTo: myButton.trailingAnchor).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
handleChangeButton()
set variable to control button state and write the function:
#objc fileprivate func handleChangeButton() {
if buttonState == 0 {
myButton.setTitle( "Select Dealer" , for: .normal)
myButton.backgroundColor = .systemBlue
myLabel.text = myButton.currentTitle
buttonState = 1
} else {
myButton.setTitle( "Dealer Selected" , for: .normal)
myButton.backgroundColor = .systemGreen
myLabel.text = myButton.currentTitle
buttonState = 0
}
UserDefaults.standard.set(buttonState, forKey: "buttonState")
}
This is the result:

Adding two buttons to UIStackView horizontally one covers the other

So I'm trying to add two buttons of equal width to my view next to each other. I have used a stack view as I thought this would work better.
The second button named "Sort" is only one visible at the correct width but covers over my "Add" button leaving and empty space next to it.
I have run it without the "Sort" button and I just get the "Add" button showing across the whole stack view as I would expect so bit confused but this.
Code is below not sure what's going on with it. If think its better to not use stackView need just a little bit of advice on best way to work the constraints. Thanks everyone.
And just to say I'm working only in programmatic with this.
import UIKit
class PlacesVC: UIViewController {
let topTitle = UILabel()
let logoImage = UIImage(named: "DIYCoffeeLogoDark")
let logoImageView = UIImageView()
let addButton = UIButton()
let sortButton = UIButton()
let buttonStack = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = #colorLiteral(red: 0.9324248433, green: 0.9268818498, blue: 0.9366856217, alpha: 1)
logoImageView.image = logoImage
self.navigationItem.titleView = logoImageView
self.navigationController?.navigationBar.isHidden = false
view.addSubview(topTitle)
view.addSubview(buttonStack)
setUpTopTital()
setUpButtonStack()
}
func setUpTopTital() {
topTitle.translatesAutoresizingMaskIntoConstraints = false
topTitle.font = UIFont(name: "Futura", size: 25)
topTitle.text = "Your Saved Places"
topTitle.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
topTitle.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
topTitle.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
topTitle.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
func setUpButtonStack() {
buttonStack.translatesAutoresizingMaskIntoConstraints = false
buttonStack.addArrangedSubview(addButton)
buttonStack.addArrangedSubview(sortButton)
buttonStack.distribution = .fillProportionally
buttonStack.alignment = .center
buttonStack.axis = .horizontal
buttonStack.spacing = 20
buttonStack.topAnchor.constraint(equalTo: topTitle.bottomAnchor, constant: 20).isActive = true
buttonStack.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
buttonStack.widthAnchor.constraint(equalToConstant: view.frame.width - 40).isActive = true
buttonStack.heightAnchor.constraint(equalToConstant: 30).isActive = true
setUpAddButton()
setUpSortButton()
}
func setUpAddButton() {
addButton.translatesAutoresizingMaskIntoConstraints = false
addButton.titleLabel?.font = UIFont(name: "Futura", size: 20)
addButton.setTitle("ADD", for: .normal)
addButton.backgroundColor = #colorLiteral(red: 0.3176470697, green: 0.07450980693, blue: 0.02745098062, alpha: 1)
addButton.layer.cornerRadius = 5
addButton.setTitleColor(.white, for: .normal)
}
func setUpSortButton() {
addButton.translatesAutoresizingMaskIntoConstraints = false
addButton.titleLabel?.font = UIFont(name: "Futura", size: 20)
addButton.setTitle("SORT", for: .normal)
addButton.backgroundColor = #colorLiteral(red: 0.3176470697, green: 0.07450980693, blue: 0.02745098062, alpha: 1)
addButton.layer.cornerRadius = 5
addButton.setTitleColor(.white, for: .normal)
}
In both functions, setUpAddButton() and setUpSortButton() you are using same button that is addButton.
Change addButton to sortButton inside setUpSortButton(), then you are able to see both buttons in your stackview.
func setUpSortButton() {
sortButton.translatesAutoresizingMaskIntoConstraints = false
sortButton.titleLabel?.font = UIFont(name: "Futura", size: 20)
sortButton.setTitle("SORT", for: .normal)
sortButton.backgroundColor = #colorLiteral(red: 0.3176470697, green: 0.07450980693, blue: 0.02745098062, alpha: 1)
sortButton.layer.cornerRadius = 5
sortButton.setTitleColor(.white, for: .normal)
}

How to combine repetitive code that just styles segmented control styling?

Beginner here with a beginner question:
I have 3 segmented controls in my view controller and I have 3 nearly identical functions that sets their colors/styling/animation. I know I shouldn't repeat code, but I'm not entirely sure how to combine these. It's prob something obvious but I need to see it done so I can do it other parts of my app. Could someone show me for this example:
class Example: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var textSegmentedControl: UISegmentedControl!
#IBOutlet weak var translationSegmentedControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
setMainSegmentedControlStyle()
setTextSegmentedControlStyle()
setTranslationSegmentedControlStyle()
}
func setTextSegmentedControlStyle() {
textSegmentedControl.backgroundColor = .clear
textSegmentedControl.tintColor = .clear
textSegmentedControl.setTitleTextAttributes([NSAttributedStringKey.font : UIFont.systemFont(ofSize: 16), NSAttributedStringKey.foregroundColor: UIColor.lightGray
], for: .normal)
textSegmentedControl.setTitleTextAttributes([NSAttributedStringKey.font : UIFont.systemFont(ofSize: 16),
NSAttributedStringKey.foregroundColor: UIColor.darkGray
], for: .selected)
let textSegmentedUnderline = UIView()
textSegmentedUnderline.translatesAutoresizingMaskIntoConstraints = false // false since we are using auto layout constraints
textSegmentedUnderline.backgroundColor = #colorLiteral(red: 0.992502749, green: 0.532302916, blue: 0.08773707598, alpha: 1)
view.addSubview(textSegmentedUnderline)
textSegmentedUnderline.topAnchor.constraint(equalTo: textSegmentedControl.bottomAnchor).isActive = true
textSegmentedUnderline.heightAnchor.constraint(equalToConstant: 3).isActive = true
textSegmentedUnderline.leftAnchor.constraint(equalTo: textSegmentedControl.leftAnchor).isActive = true
textSegmentedUnderline.widthAnchor.constraint(equalTo: textSegmentedControl.widthAnchor, multiplier: 1 / CGFloat(textSegmentedControl.numberOfSegments)).isActive = true
UIView.animate(withDuration: 0.3) {
self.textSegmentedUnderline.frame.origin.x = (self.textSegmentedControl.frame.width / CGFloat(self.textSegmentedControl.numberOfSegments)) * CGFloat(self.textSegmentedControl.selectedSegmentIndex)
}
}
func setTranslationSegmentedControlStyle() {
translationSegmentedControl.backgroundColor = .clear
translationSegmentedControl.tintColor = .clear
translationSegmentedControl.setTitleTextAttributes([NSAttributedStringKey.font : UIFont.systemFont(ofSize: 16), NSAttributedStringKey.foregroundColor: UIColor.lightGray
], for: .normal)
translationSegmentedControl.setTitleTextAttributes([NSAttributedStringKey.font : UIFont.systemFont(ofSize: 16), NSAttributedStringKey.foregroundColor: UIColor.darkGray
], for: .selected)
let translationSegmentedUnderline = UIView()
translationSegmentedUnderline.translatesAutoresizingMaskIntoConstraints = false // false since we are using auto layout constraints
translationSegmentedUnderline.backgroundColor = #colorLiteral(red: 0.992502749, green: 0.532302916, blue: 0.08773707598, alpha: 1)
view.addSubview(translationSegmentedUnderline)
translationSegmentedUnderline.topAnchor.constraint(equalTo: textSegmentedControl.bottomAnchor).isActive = true
translationSegmentedUnderline.heightAnchor.constraint(equalToConstant: 3).isActive = true
translationSegmentedUnderline.leftAnchor.constraint(equalTo: translationSegmentedControl.leftAnchor).isActive = true
translationSegmentedUnderline.widthAnchor.constraint(equalTo: translationSegmentedControl.widthAnchor, multiplier: 1 / CGFloat(translationSegmentedControl.numberOfSegments)).isActive = true
UIView.animate(withDuration: 0.3) {
self.translationSegmentedUnderline.frame.origin.x = (self.textSegmentedControl.frame.width / CGFloat(self.translationSegmentedControl.numberOfSegments)) * CGFloat(self.translationSegmentedControl.selectedSegmentIndex)
}
}
func setMainSegmentedControlStyle() {
segmentedControl.backgroundColor = .clear
segmentedControl.tintColor = .clear
segmentedControl.setTitleTextAttributes([NSAttributedStringKey.font : UIFont.systemFont(ofSize: 16), NSAttributedStringKey.foregroundColor: UIColor.lightGray
], for: .normal)
segmentedControl.setTitleTextAttributes([NSAttributedStringKey.font : UIFont.systemFont(ofSize: 16),
NSAttributedStringKey.foregroundColor: UIColor.darkGray
], for: .selected)
let buttonBar = UIView()
buttonBar.translatesAutoresizingMaskIntoConstraints = false // false since we are using auto layout constraints
buttonBar.backgroundColor = #colorLiteral(red: 0.992502749, green: 0.532302916, blue: 0.08773707598, alpha: 1)
view.addSubview(buttonBar)
buttonBar.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor).isActive = true
buttonBar.heightAnchor.constraint(equalToConstant: 3).isActive = true
buttonBar.leftAnchor.constraint(equalTo: segmentedControl.leftAnchor).isActive = true
buttonBar.widthAnchor.constraint(equalTo: segmentedControl.widthAnchor, multiplier: 1 / CGFloat(segmentedControl.numberOfSegments)).isActive = true
UIView.animate(withDuration: 0.3) {
self.buttonBar.frame.origin.x = (self.segmentedControl.frame.width / CGFloat(self.segmentedControl.numberOfSegments)) * CGFloat(self.segmentedControl.selectedSegmentIndex)
}
}
}
If all the styling code is exactly the same, you can declare a function that accepts a control as an argument:
func style(control: UISegmentedControl) {
control.backgroundColor = .clear
// continue styling here
}
Then simply call that function however many times you need, passing in each control:
style(control: segmentedControl)
style(control: textSegmentedControl)
style(control: translationSegmentedControl)
Note, though, that you should set as much as you can (such as backgroundColor) in your Storyboard rather than code.

Swift iOS -Constraint isn't animating even though view.layoutIfNeeded() is called

I have a rounded likeButton that is behind a rounded moreButton. The likeButton is pinned to the centerX and centerY of the moreButton. When I press the moreButton I want to animate the likeButton 200 points above the moreButton.
I use a NSLayoutConstraint to keep track of the likeButton's centerY and to make changes to it. When I call UIView.animate and inside it's closure I call self.view.layoutIfNeeded() the view isn't updating.
#objc func moreButtonTapped(){
likeButtonCenterY?.isActive = false
likeButton.centerYAnchor.constraint(equalTo: self.moreButton.centerYAnchor, constant: -200)
likeButtonCenterY?.isActive = true
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
Where am I going wrong?
let moreButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "more"), for: .normal)
button.tintColor = .red
button.clipsToBounds = true
button.addTarget(self, action: #selector(moreButtonTapped), for: .touchUpInside)
return button
}()
let likeButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "like"), for: .normal)
button.tintColor = .blue
button.clipsToBounds = true
return button
}()
var likeButtonCenterY: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setAnchors()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
moreButton.layer.cornerRadius = moreButton.frame.size.width / 2
likeButton.layer.cornerRadius = likeButton.frame.size.width / 2
}
#objc func moreButtonTapped(){
likeButtonCenterY?.isActive = false
likeButton.centerYAnchor.constraint(equalTo: self.moreButton.centerYAnchor, constant: -200)
likeButtonCenterY?.isActive = true
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
func setAnchors(){
view.addSubview(likeButton)
view.addSubview(moreButton)
moreButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
moreButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -200).isActive = true
moreButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
moreButton.heightAnchor.constraint(equalToConstant: 100).isActive = true
likeButtonCenterY = likeButton.centerYAnchor.constraint(equalTo: moreButton.centerYAnchor)
likeButtonCenterY?.isActive = true
likeButton.centerXAnchor.constraint(equalTo: moreButton.centerXAnchor).isActive = true
likeButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
likeButton.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
Try this
#objc func moreButtonTapped(){
likeButtonCenterY?.isActive = false
likeButtonCenterY = likeButton.centerYAnchor.constraint(equalTo: self.moreButton.centerYAnchor, constant: -200)
likeButtonCenterY?.isActive = true
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
You have forgotten to write likeButtonCenterY =
If you are activating/deactivating multiple constraints at the same time (like a batch), you might need to override the updateConstraints() method. More information is here: https://developer.apple.com/documentation/uikit/uiview/1622512-updateconstraints
Basically your code in the moreButtonTapped() will go in the overridden implementation of the updateConstraints() method. Don't forget to call super() in the last line of the method.
Then your animation code should look like look like:
UIView.animate(withDuration: 0.3) {
self.view.setNeedsUpdateConstraints()
self.view.layoutIfNeeded()
})

Resources