Buttons Programmatically on Swift 4 - swift

A quick question, I am trying to create a function called setupHeader() in which when called, setups up all the views and buttons of the header. The problem is with the Log Out button, for some reason I cannot get it right.
The first error is that the addTarget of the button doesn't recognize "self". Any suggestions?
The second one is that the #selector only works with the #objc func and the function cannot be in the main setupHeader function. Where should I put it? here is a snippet of my code:
import Foundation
import UIKit
func setupHeader(vc: UIViewController) {
let headerFiller: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = supaGray
return view
}()
let header: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = supaGray
return view
}()
let logOutButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Log Out", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .left
button.addTarget(self, action: #selector(handleLogOutButton), for: .touchUpInside)
return button
}()
func handleLogOutButton() {
}
vc.view.addSubview(header)
header.topAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.topAnchor).isActive = true
header.leftAnchor.constraint(equalTo: vc.view.leftAnchor).isActive = true
header.rightAnchor.constraint(equalTo: vc.view.rightAnchor).isActive = true
header.heightAnchor.constraint(equalToConstant: 40).isActive = true
header.addSubview(logOutButton)
logOutButton.centerYAnchor.constraint(equalTo: header.centerYAnchor, constant: -5).isActive = true
logOutButton.leftAnchor.constraint(equalTo: header.leftAnchor, constant: 7).isActive = true
logOutButton.widthAnchor.constraint(equalTo: header.widthAnchor, multiplier: 0.17).isActive = true
logOutButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
vc.view.addSubview(headerFiller)
headerFiller.topAnchor.constraint(equalTo: vc.view.topAnchor).isActive = true
headerFiller.leftAnchor.constraint(equalTo: vc.view.leftAnchor).isActive = true
headerFiller.rightAnchor.constraint(equalTo: vc.view.rightAnchor).isActive = true
headerFiller.bottomAnchor.constraint(equalTo: header.topAnchor).isActive = true
}

Declare your button press callback this way.
#objc func handleLogOutButton() {
}
And For self error use vc as your passing as argument above in function.

It looks like you're passing a view controller into your function so using self won't work. Try using 'vc'

Pretty straight forward:
Modify your code as shown below:
lazy var logOutButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Log Out", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .left
button.addTarget(self, action: #selector(handleLogOutButton), for: .touchUpInside)
return button
}()
#objc func handleLogOutButton() {
// Do your things here
}

Related

#selector(didTapRegister) not in scope

When I run this code by #selector(didTapRegister) I get a didTapRegister is not in scope. I have a bigger application I am using it in and on one page it works and another it won't, well it won't work anywhere else.
I am stuck on figuring out why. Can anyone help solve it? seems to be an Objective-C issue possibly because I have same issues trying to make IBOutlets, I have tried searching for answers and I just don't have enough knowledge to understand why it isn't working, I've tried moving the code to different scopes and still nothing. with the IBOutlets it's saying IBOutlet property cannot have non-object type UIButton?.Type
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let Button = UIButton.init(type: .custom)
Button.setTitle("Chat", for: .normal)
Button.layer.backgroundColor = CGColor.init(gray: 0.0, alpha: 0.0)
Button.setTitleColor(.white, for: .normal)
Button.setTitleColor(.red, for: .highlighted)
Button.layer.borderWidth = 1
Button.layer.cornerRadius = 5
Button.layer.borderColor = UIColor.white.cgColor
Button.isHighlighted = false
Button.showsTouchWhenHighlighted = true
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: Button)
Button.addTarget(self, action: #selector(didTapRegister), for: .touchUpInside)
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.isTranslucent = true
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Do any additional setup after loading the view.
}
}
}
Maybe try what he said but
Just use SwiftUI it is much better
#objc and all of that is fine but in SwiftUI you can just right click and add buttons
There 2 mistakes in the code.
You have to create #objc function for the button action
In your code move viewWillAppear method outside the viewDidLoad
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton.init(type: .custom)
button.setTitle("Chat", for: .normal)
button.setTitleColor(.white, for: .normal)
button.setTitleColor(.red, for: .highlighted)
button.layer.borderWidth = 1
button.layer.cornerRadius = 5
button.layer.borderColor = UIColor.white.cgColor
button.isHighlighted = false
button.showsTouchWhenHighlighted = true
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)
button.addTarget(self, action: #selector(didTapRegister), for: .touchUpInside)
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.isTranslucent = true
}
#objc func didTapRegister(_ sender: UIButton) {
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Do any additional setup after loading the view.
}
}
Note: button name start with lowercase.

UISegmentedControl: segment overlapping when pass data back

thanks Duncan C. I edit my question:
i have a ViewControllerA with a 3 segments SegmentedControl, on ViewControllerB you can delete one segment and go back to VCA.
when I do so, I have, in VCA, 2 segments (the new segments after deletion) over the 3 segments (the 3 segments initially loaded).
VCA before change
VCA after change
I understand that it has to do with the view cycle, the initial ViewDidLoad persists, but even passing through viewWillAppear I still have this result.
this project is not a real project, just a test to solve the problem I have on my initial project.
my code for VCA:
class VCA: UIViewController, Info {
// array which contains the name for the segmentedControl
var array = ["Peter", "Bob", "Jim"]
var segmentedControl: UISegmentedControl!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setView(array: array)
}
override func viewDidLoad() {
super.viewDidLoad()
}
// delegate protocol: receive data from VCB
func passData(array: [String]) {
self.array = array
}
func setView(array: [String]) {
segmentedControl = UISegmentedControl(items: array)
segmentedControl.selectedSegmentIndex = 0
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("OK", for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 50
stackView.addArrangedSubview(segmentedControl)
stackView.addArrangedSubview(button)
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20)])
}
#objc func buttonTapped() {
let destinationVC = VCB()
destinationVC.array = array
destinationVC.infoDelegate = self
destinationVC.modalPresentationStyle = .fullScreen
present(destinationVC, animated: true, completion: nil)
}
}
my code for VCB:
class VCB: UIViewController {
// array which contains the name of the segments
var array = [String]()
// delegate to send data back
var infoDelegate: Info!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
setView(array: array)
}
func setLabel(array: [String]) -> String {
var labelText = "the array contains"
for name in array {
labelText += " \(name)"
}
return labelText
}
func setView(array: [String]) {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.text = setLabel(array: array)
view.addSubview(label)
view.backgroundColor = .cyan
NSLayoutConstraint.activate([
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
label.centerXAnchor.constraint(equalTo: view.centerXAnchor)])
let button = UIButton(type: .system)
button.setTitle("Delete the first name in the array", for: .normal)
button.addTarget(self, action: #selector(backTapped), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 40),
button.centerXAnchor.constraint(equalTo: label.centerXAnchor)])
}
#objc func backTapped() {
array.remove(at: 0)
infoDelegate.passData(array: array)
dismiss(animated: true, completion: nil)
}
}
with a protocol to send data back to VCB:
protocol Info {
func passData(array: [String])
}
any ideas/help?
thanks a lot
I gather VCA is view controller A and VCB is view controller B. You're saying that from VCB you delete one segment of VCA's segmented controller?
You should not mess with another view controller's views.
You will need to post your code and explain how the user navigates between view controllers in order for us to be able to help you.
(And by the way, those huge segmented controls look absurd. You should leave them at the standard height.)
I think I have found a solution. I remove all segments of the UISegment before repopulating it with the new data received from ViewController B.
func passData(array: [String]) {
self.array =array
segmentedControl.removeAllSegments()
segmentedControl = UISegmentedControl(items: array)
}
If the solution seems to work, it is not very elegant.

Button Doesn't Work on iPhone X or XR But Works on Simulator

I have a button that when clicked turns into a checkmark. It works on all simulators including X and XR but not on an actual X or XR device (according to friends using TestFlight). It works on several other devices though. Wondering if there's anything to do with X and XR specifically or may be constraints, clips to bounds? The problem is it isn't showing the checkmark or adding the value associated with its cell to an array I have, so I don't think it's selectable on those phones.
let button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.layer.masksToBounds = true
button.isUserInteractionEnabled = true
button.clipsToBounds = true
button.contentMode = .scaleAspectFit
return button
}()
Constraints
button.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
button.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -(width/40)).isActive = true
button.widthAnchor.constraint(equalToConstant: width/8).isActive = true
Gesture Recognizer
private func setupGestureRecognizer() {
let joinButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(joinButtonTapped))
button.addGestureRecognizer(joinButtonTapGestureRecognizer)
}
#objc private func joinButtonTapped() {
delegate?.joinButtonTapped(inCell: self, button: button)
}
JoinButtonTapped -> Plus Button to Checkmark and adds group name to array
extension SearchGroupsVC: GroupsViewCellDelegate {
func joinButtonTapped(inCell cell: UITableViewCell, button: UIButton) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
if searchBar.text?.count != 0 {
group = filteredTypes[indexPath.row]
} else {
group = allGroups[indexPath.row]
}
if selected.contains(group) {
if self.traitCollection.userInterfaceStyle == .dark {
button.setImage(UIImage(named: "plus_white"), for: .normal)
} else {
button.setImage(UIImage(named: "plus_black"), for: .normal)
}
selected.remove(at: selected.index(of: group)!)
} else if !selected.contains(group){
if self.traitCollection.userInterfaceStyle == .dark {
button.setImage(UIImage(named: "checkmark_white"), for: .normal)
} else {
button.setImage(UIImage(named: "checkmark_black"), for: .normal)
}
selected.append(group)
}
}
}
Not sure if this will work, but you should use addTarget instead of a gesture recognizer:
// let joinButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(joinButtonTapped))
// button.addGestureRecognizer(joinButtonTapGestureRecognizer)
// remove this ^
button.addTarget(self, action: #selector(joinButtonTapped), for: .touchUpInside)

How can I use different type of cell in collection view?

I am trying create an app about social network. In there users can send two different type post (like image post or text post). Users selecting of their post type in another controller and create their post. I Created two different cell and want to call in cellForItemAt with if statement. The problem is how can I know users selection about post type ?
// there is users post type selection with button
let writeButton: UIButton = {
let wButton = UIButton()
wButton.layer.cornerRadius = 7
wButton.setBackgroundImage(#imageLiteral(resourceName: "writing"), for: .normal)
wButton.setTitle("Writing", for: .normal)
wButton.setTitleColor(UIColor.white, for: .normal)
wButton.titleLabel?.font = UIFont(name: "AvenirNext-DemiBoldItalic", size: 18)
wButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
wButton.layer.borderWidth = 1.5
wButton.addTarget(self, action: #selector(tappedWriteButton), for: .touchUpInside)
return wButton
}()
#objc func tappedWriteButton () {
let writePost = WriteTitlePost()
navigationController?.pushViewController(writePost, animated: true)
}
let mediaButton: UIButton = {
let mButton = UIButton()
mButton.layer.cornerRadius = 7
mButton.backgroundColor = UIColor.orange
mButton.setBackgroundImage(#imageLiteral(resourceName: "photography"), for: .normal)
mButton.setTitle("Media Post", for: .normal)
mButton.setTitleColor(UIColor.white, for: .normal)
mButton.titleLabel?.font = UIFont(name: "AvenirNext-DemiBoldItalic", size: 18)
mButton.layer.borderWidth = 1.5
mButton.layer.borderColor = UIColor.orange.cgColor
mButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
mButton.addTarget(self, action: #selector(tappedMediaButton), for: .touchUpInside)
return mButton
}()
#objc func tappedMediaButton() {
let layout = UICollectionViewFlowLayout()
let mediaPost = MediaPost(collectionViewLayout: layout)
let navController = UINavigationController(rootViewController: mediaPost)
present(navController, animated: true, completion: nil)
}

Going back to the previous page programatically

enter image description hereI am making a user login page for my IOS App. I am trying to make everything programmatically without using a segue in the main story board. I have managed to go to the next page as you can see in the code and create a custom left back bar item. But I don't know how to go back to the first page when I click on that button. I have attached the screen shot of my story board and app below. Can you please tell me how I can go back to the first page by clicking the X button?
Here is my code:
import UIKit
class FirstPage: UIViewController {
let hgt = UIScreen.main.bounds.height/2
private let loginSignup: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Login or Sign Up", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
button.setTitleColor(.green, for: .normal)
button.addTarget(self, action: #selector(loginSignupBtn), for: .touchUpInside)
return button
}()
#objc private func loginSignupBtn() {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "Login") as! Login
vc.loadViewIfNeeded()
self.navigationController?.pushViewController(vc, animated: true)
self.navigationController?.isNavigationBarHidden = false
let backItem = UIBarButtonItem(title: "X ", style: UIBarButtonItem.Style.done, target: nil, action: nil)
let font = UIFont.boldSystemFont(ofSize: 26)
backItem.setTitleTextAttributes([NSAttributedString.Key.font:font] ,for: .normal)
backItem.tintColor = UIColor.green
vc.navigationItem.leftBarButtonItem = backItem
}
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
}
override func viewWillAppear(_ animated: Bool) {
}
private func setupLayout() {
let topImageContainerView = UIView()
//topImageContainerView.backgroundColor = .yellow
view.addSubview(topImageContainerView)
//enable auto layout
topImageContainerView.translatesAutoresizingMaskIntoConstraints = false
topImageContainerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
topImageContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
topImageContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
topImageContainerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
view.addSubview(loginSignup)
loginSignup.translatesAutoresizingMaskIntoConstraints = false
loginSignup.topAnchor.constraint(equalTo: view.topAnchor,constant:hgt/2).isActive = true
loginSignup.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
loginSignup.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
loginSignup.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
}
}
enter image description here
Here you need to add target and selector for when the x button is clicked
let backItem = UIBarButtonItem(title: "X ",
style: UIBarButtonItem.Style.done,
target: self, action:#selector(goBack))
#objc func goBack(_ bar:UIBarButtonItem) {
self.navigationController?.popToRootViewController(animated:true)
}