How to add a button at the bottom in sidemenu swift? - ios

I have a design in which the sign-out button is placed at the bottom and should not scroll, side menu has a tableview controller in which we can add rows but my requirement is to add the sign-out button at the bottom. I have tried by adding the sign-out button in the footer view to side menu tableview controller, but showing just bellow the rows which I don't want.
import UIKit
import SideMenu
class ProfileViewController: UIViewController {
var menu:SideMenuNavigationController?
override func viewDidLoad() {
super.viewDidLoad()
configureSideMenu()
}
func configureSideMenu() {
menu = SideMenuNavigationController(rootViewController: MenuListController())
menu?.navigationBar.setBackgroundImage(UIImage(named: "top_navbrBG"), for: .default)
let firstFrame = CGRect(x: 20, y: 0, width: menu?.navigationBar.frame.width ?? 0/2, height: menu?.navigationBar.frame.height ?? 0)
let firstLabel = UILabel(frame: firstFrame)
firstLabel.text = "Settings"
menu?.navigationBar.addSubview(firstLabel)
SideMenuManager.default.addPanGestureToPresent(toView: self.view)
let screenSize = UIScreen.main.bounds
let screenHeight = screenSize.height + 40
let leftBorderView = UIView(frame: CGRect(x: 1, y: -40, width: 1, height: screenHeight))
leftBorderView.backgroundColor = UIColor.init(hexString: "#cfcfcf")
menu?.navigationBar.addSubview(leftBorderView)
}
#IBAction func menuButtonAction(_ sender: UIButton) {
if let menu = menu {
present(menu, animated: true)
}
}
}
// functions
extension ProfileViewController {
#objc func signOutButtonTapped(_ sender: AnyObject?) {
print("sigin out")
}
func getSignOutButton()->UIButton {
let button = UIButton()
button.setTitle("Sign out", for: .normal)
let color = UIColor.init(hexString: "#1A73E9")
button.setTitleColor(color, for: .normal)
button.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)
return button
}
func setConstraintsForSignOutButton(button: UIButton) {
let screenSize = UIScreen.main.bounds
let screenHeight = screenSize.height
guard let menuTopAnchor = menu?.navigationBar.topAnchor else { return }
guard let menuLeadingAnchor = menu?.navigationBar.leadingAnchor else { return }
guard let menuTrailingAnchor = menu?.navigationBar.trailingAnchor else { return }
guard let menuWidth = menu?.menuWidth else { return }
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: menuLeadingAnchor, constant: 0),
button.trailingAnchor.constraint(equalTo: menuTrailingAnchor, constant: 0),
button.widthAnchor.constraint(equalToConstant: menuWidth),
button.heightAnchor.constraint(equalToConstant: 40),
button.bottomAnchor.constraint(equalTo: menuTopAnchor, constant: 300)
])
}
}
// Menu Items
class MenuListController: UITableViewController {
var menuItems = [[String: String]]()
override func viewDidLoad() {
super.viewDidLoad()
menuItems.append(["name": "Privacy", "img": "privacy", "key" : "privacy"])
menuItems.append(["name": "Report issue", "img": "report_issue", "key": "report_issue"])
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.tableFooterView = getSignOutButton()
}
#objc func signOutButtonTapped(_ sender: AnyObject?) {
print("sigin out")
}
func getSignOutButton()->UIButton {
let button = UIButton()
button.height = 20
button.width = 100
button.setTitle("Sign out", for: .normal)
let color = UIColor.init(hexString: "#1A73E9")
button.setTitleColor(color, for: .normal)
button.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)
return button
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
menuItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if menuItems.indices.contains(indexPath.row) {
let dict = menuItems[indexPath.row]
cell.textLabel?.text = dict["name"]
if let img = dict["img"] {
cell.imageView?.image = UIImage(named: img)
}
}
return cell
}
}
desired design

Replace this "tableView.tableFooterView = getSignOutButton()" with
self.getSignOutButton() in viewDidLoad.
Next replace your function "getSignOutButton()->UIButton {}" with below code.
func getSignOutButton() {
let button = UIButton()
button.setTitle("Sign out", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
button.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
button.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor,constant: -20),
button.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
])
}

Related

Show UIView When Popping Back on a Navigation Controller

So I'm trying to show an UIView in a stack view when I click continue on a controller and pop back to the previous controller. However, I'm having trouble showing the view when I pop. It will show if I present the controller, but I need it to show when I pop. How should I go about this? Thank you.
// ServiceDetailController
// MARK: - Properties
lazy var dateContainer: ShadowCardView = {
let view = ShadowCardView()
view.backgroundColor = .white
view.addShadow()
view.setHeight(height: 40)
let stack = UIStackView(arrangedSubviews: [calendarIcon, dateLabel, timeOfDayLabel])
stack.axis = .horizontal
stack.distribution = .fillProportionally
stack.spacing = 8
view.addSubview(stack)
stack.centerY(inView: view)
stack.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 12, paddingRight: 70)
view.addSubview(closeButton)
closeButton.centerY(inView: view)
closeButton.anchor(right: view.rightAnchor, paddingRight: 12)
return view
}()
lazy var dateStack = UIStackView(arrangedSubviews: [dateLabelStack, dateContainer, dateContainerView])
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
// MARK: - Selectors
#objc func handleDateCreationTapped() {
let controller = DateCreationController()
controller.jobService = self.jobService
navigationController?.pushViewController(controller, animated: true)
}
// MARK: - Helper Functions
fileprivate func configureUI() {
setupNavigationBar()
view.backgroundColor = .groupTableViewBackground
setupServiceInfoView()
setupFormView()
}
fileprivate func setupFormView() {
showDataContainer(shouldShow: false)
setupTapGestureRecognizers()
}
fileprivate func setupTapGestureRecognizers() {
let dateTap = UITapGestureRecognizer(target: self, action: #selector(handleDateCreationTapped))
dateTap.numberOfTapsRequired = 1
dateTextField.addGestureRecognizer(dateTap)
}
func showDateContainer(shouldShow: Bool) {
if shouldShow {
dateContainer.isHidden = false
dateStack.spacing = 5
} else {
dateContainer.isHidden = true
dateStack.spacing = -18
}
}
// DateCreationController
// MARK: - Properties
private let continueButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Continue", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont(name: "AvenirNext-Medium", size: 14)
button.backgroundColor = .darkGray
button.setHeight(height: 50)
button.layer.cornerRadius = 8
button.addTarget(self, action: #selector(handleContinue), for: .touchUpInside)
return button
}()
// MARK: - Selectors
#objc func handleContinue() {
let controller = ServiceDetailController()
controller.showDateContainer(shouldShow: true)
navigationController?.popViewController(animated: true)
}
The main problem is this func in your DateCreationController:
#objc func handleContinue() {
// here, you are creating a NEW instance of ServiceDetailController
let controller = ServiceDetailController()
controller.showDateContainer(shouldShow: true)
// here, you are popping back to where you came from... the EXISTING instance of ServiceDetailController
navigationController?.popViewController(animated: true)
}
You need to use either delegate/protocol pattern or a closure.
Here's an example using a closure...
Add this var to your DateCreationController class:
var continueCallback: ((Bool)->())?
In your ServiceDetailController class, when you instantiate and push your DateCreationController, you'll also setup that closure:
#objc func handleDateCreationTapped() {
let controller = DateCreationController()
controller.jobService = self.jobService
// add the closure
controller.continueCallback = { [weak self] shouldShow in
guard let self = self else { return }
self.showDateContainer(shouldShow: shouldShow)
self.navigationController?.popViewController(animated: true)
}
navigationController?.pushViewController(controller, animated: true)
}
Then, in your button action in DateCreationController, you "call back" using that closure:
#objc func handleContinue() {
continueCallback?(true)
}
Here's a runnable example. It creates a simple yellow view as the dateContainer view, but it is hidden on load. Tapping anywhere will push to DateCreationController, which has a single "Continue" button. Tapping that button will execute the closure, where the dateContainer view will be set to visible and we'll pop back:
class ServiceDetailController: UIViewController {
var jobService: String = "abc"
// plain yellow view
let dateContainer: UIView = {
let view = UIView()
view.backgroundColor = .yellow
return view
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
let infoLabel = UILabel()
infoLabel.text = "Tap anywhere"
view.addSubview(infoLabel)
view.addSubview(dateContainer)
infoLabel.translatesAutoresizingMaskIntoConstraints = false
dateContainer.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
infoLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
dateContainer.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0),
dateContainer.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
dateContainer.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
dateContainer.heightAnchor.constraint(equalToConstant: 100.0),
])
setupTapGestureRecognizers()
// hide dateContainer on load
showDateContainer(shouldShow: false)
}
// MARK: - Selectors
#objc func handleDateCreationTapped() {
let controller = DateCreationController()
controller.jobService = self.jobService
// add the closure
controller.continueCallback = { [weak self] shouldShow in
guard let self = self else { return }
self.showDateContainer(shouldShow: shouldShow)
self.navigationController?.popViewController(animated: true)
}
navigationController?.pushViewController(controller, animated: true)
}
// MARK: - Helper Functions
fileprivate func setupTapGestureRecognizers() {
let dateTap = UITapGestureRecognizer(target: self, action: #selector(handleDateCreationTapped))
dateTap.numberOfTapsRequired = 1
view.addGestureRecognizer(dateTap)
}
func showDateContainer(shouldShow: Bool) {
if shouldShow {
dateContainer.isHidden = false
} else {
dateContainer.isHidden = true
}
}
}
class DateCreationController: UIViewController {
var jobService: String = "abc"
var continueCallback: ((Bool)->())?
lazy var continueButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Continue", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont(name: "AvenirNext-Medium", size: 14)
button.backgroundColor = .darkGray
button.layer.cornerRadius = 8
button.addTarget(self, action: #selector(handleContinue), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
view.addSubview(continueButton)
continueButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
continueButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
continueButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
continueButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.75),
continueButton.heightAnchor.constraint(equalToConstant: 50.0),
])
}
// MARK: - Selectors
#objc func handleContinue() {
continueCallback?(true)
}
}

Protocol and delegate pattern not calling the method

Implementing a protocol/delegate pattern as suggested by expert #DonMag, i am unable to get the code working...
I show a ratings window like below and when the user changes the ratings , i want the ratings emoji to update and the controller to pop, i also use animation, now for view i have a separate ratings class and for controller a separate class,
The problem is this function gets tapped , i can detect it but it does not reach the protocol method
#objc func ratingButtonTapped(_ sender: UIButton) {
// print(sender.titleLabel?.text)
guard let t = sender.titleLabel?.text else {
return
}
ratingDelegate?.defineTheratings(t)
}
Controller class
import UIKit
import CoreData
protocol RatingsPresentation: class {
func defineTheratings(_ ratings: String)
}
class RatingsViewController: UIViewController, RatingsPresentation {
func defineTheratings(_ ratings: String) {
print("ratings seleced\(ratings)")
self.restaurant.rating = ratings
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
appDelegate.saveContext()
}
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
else {
self.navigationController?.popViewController(animated: true)
}
}
var restaurant: Restaurant!
var rates = RatingsView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rates.view)
//Delegates
let vc = RatingsView()
// set the rating delegate
vc.ratingDelegate = self
//
if let restaurantImage = restaurant.image {
rates.bkgImageView.image = UIImage(data: restaurantImage as Data)
}
rates.crossBtn.addTarget(self, action: #selector(closeRatings), for: .touchUpInside)
let animateCross = CGAffineTransform(translationX: 1000, y: 0)
rates.crossBtn.transform = animateCross
}
#objc func closeRatings() {
navigationController?.popViewController(animated: true)
}
}
View Class
import UIKit
class RatingsView: UIViewController {
var bkgImageView = UIImageView()
var crossBtn = UIButton()
var btnCollection: [UIButton] = []
weak var ratingDelegate: RatingsPresentation?
override func viewDidLoad() {
super.viewDidLoad()
let ratings: [String] = ["cool", "happy","love", "sad", "angry"]
let animationBlur = UIBlurEffect(style: .dark)
let visualBlurView = UIVisualEffectView(effect: animationBlur)
// add elements to self
view.addSubview(bkgImageView)
view.addSubview(visualBlurView)
bkgImageView.translatesAutoresizingMaskIntoConstraints = false
visualBlurView.translatesAutoresizingMaskIntoConstraints = false
bkgImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
bkgImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
bkgImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
bkgImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// constrain visualBlurView to all 4 sides
visualBlurView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualBlurView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
visualBlurView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
visualBlurView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
bkgImageView.contentMode = .scaleAspectFill
// Do any additional setup after loading the view.
//Add stackView
let stackEmoji = UIStackView()
stackEmoji.translatesAutoresizingMaskIntoConstraints = false
stackEmoji.axis = .vertical
stackEmoji.spacing = 5
stackEmoji.distribution = .fill
stackEmoji.alignment = .top
let font = UIFont(name: "Rubik-Medium", size: 28)
let fontM = UIFontMetrics(forTextStyle: .body)
ratings.forEach { (btn) in
let b = UIButton()
b.setTitle(btn, for: .normal)
b.titleLabel?.font = fontM.scaledFont(for: font!)
b.setImage(UIImage(named: btn), for: .normal)
stackEmoji.addArrangedSubview(b)
btnCollection.append(b)
//btn animation
let sizeAnimation = CGAffineTransform(scaleX: 5, y: 5)
let positionAnimation = CGAffineTransform(translationX: 1000, y: 0)
let combinedAninaton = sizeAnimation.concatenating(positionAnimation)
b.transform = combinedAninaton
b.addTarget(self, action: #selector(ratingButtonTapped(_:)), for: .touchUpInside)
}
view.addSubview(stackEmoji)
stackEmoji.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackEmoji.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// if let img = UIImage(named: "cross") {
// crossBtn.setImage(img, for: [])
// }
crossBtn.setTitle("X", for: [])
crossBtn.setTitleColor(.white, for: .normal)
crossBtn.setTitleColor(.gray, for: .highlighted)
crossBtn.titleLabel?.font = UIFont.systemFont(ofSize: 44, weight: .bold)
view.addSubview(crossBtn)
crossBtn.translatesAutoresizingMaskIntoConstraints = false
crossBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
crossBtn.topAnchor.constraint(equalTo: view.topAnchor, constant: 150).isActive = true
}
#objc func ratingButtonTapped(_ sender: UIButton) {
// print(sender.titleLabel?.text)
guard let t = sender.titleLabel?.text else {
return
}
ratingDelegate?.defineTheratings(t)
}
}
Problem is here
var rates = RatingsView() //// shown 1
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rates.view) /////// you add this as a subview
//Delegates
let vc = RatingsView() //////// this is useless - not shown 1
// set the rating delegate
vc.ratingDelegate = self ////// and set the delegate for this non-shown
So you need to remove let vc = RatingsView() and set the delegate for
rates.ratingDelegate = self

Removing Elements from structure if they are blank so they are not displayed in UITableView

I have a simple gym workout App that consists of 2 view controllers (vc1 and vc2).
Users put some information into VC2, this is then this is passed to and displayed in VC1.
VC1 is a UITableView. The information in VC2 is stored in a struct and passed to VC1 via protocol delegate method.
The user inputs data in VC2 to fill the struct. Then VC1 populates itself based on the struct.
VC1 is an extendible UITableView. Users put in the title of the workout and then when the row is clicked the users can see the exercises that they entered along with their sets and reps.
my plan is to have 10 exercises sections that the users can enter.
My problem is that if the user doesn't want a workout to consist of 10 exercises, say they want the workout to consist of only 3 exercisers. So they will fill in the fields for 3 exercises only. I am left with 3 cells in the UITableView that are populated with information and 7 that are occupied by black strings.
How can I get rid of the 7 blank exercises so they will not show up in the UITableView? In this code below I have only added one exercise element not 10 yet. I just want to solve this issue first while the code isn't too big.
VC1 is called ContactsController this is the UITableView:
import Foundation
import UIKit
private let reuseidentifier = "Cell"
struct cellData {
var opened = Bool()
var title = String()
var sectionData = [String]()
}
//here
struct Contact {
var fullname: String
var excerciseOne: String
var excerciseOneReps: String
var excerciseOneSets: String
}
class ContactController: UITableViewController {
//new
var tableViewData = [cellData]()
var contacts = [Contact]()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.title = "Workouts"
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(handleAddContact))
view.backgroundColor = .white
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseidentifier)
}
#IBAction func handleAddContact(_ sender: Any) {
let controller = AddContactController()
controller.delegate = self
self.present(UINavigationController(rootViewController: controller), animated: true, completion: nil)
}
//UITABLEVIEW
//all new
override func numberOfSections(in tableView: UITableView) -> Int {
//new
return tableViewData.count
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
//old needed return contacts.count
//new
if tableViewData[section].opened == true {
return tableViewData[section].sectionData.count + 1
}else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//old needed let cell = tableView.dequeueReusableCell(withIdentifier: reuseidentifier, for: indexPath)
// cell.textLabel?.text = contacts[indexPath.row].fullname
// return cell
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseidentifier, for: indexPath)
cell.textLabel?.text = tableViewData[indexPath.section].title
return cell
}else {
//use a different cell identifier if needed
let cell = tableView.dequeueReusableCell(withIdentifier: reuseidentifier, for: indexPath)
// cell.textLabel?.text = tableViewData[indexPath.section].sectionData[indexPath.row]
cell.textLabel?.text = tableViewData[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
//did select row new
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableViewData[indexPath.section].opened == true {
tableViewData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none) //play around with animation
}else {
tableViewData[indexPath.section].opened = true
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none) //play around with animation
}
}
}
//this is an extention to addContactController. this is what happens whent he done button is clicked in addcontactcontroller
extension ContactController: AddContactDelegate {
func addContact(contact: Contact) {
self.dismiss(animated: true) {
self.contacts.append(contact)
self.tableViewData.append(cellData.init(opened: false, title: contact.fullname, sectionData: [contact.excerciseOne + " Reps: " + contact.excerciseOneReps + " Sets: " + contact.excerciseOneSets, "cell2", "cell3"]))
self.tableView.reloadData()
}
}
}
The Following is my VC2 where the user inputs the data to create a workout and its associated exercises:
import Foundation
import UIKit
//here
protocol AddContactDelegate {
func addContact(contact: Contact)
}
class AddContactController: UIViewController {
//delegate
var delegate: AddContactDelegate?
//Title TextField
let titleTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "What you you like to call your workout?"
tf.textAlignment = .center
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
//Exercise 1 title TextField
let excercise1TextField: UITextField = {
let ex1 = UITextField()
ex1.placeholder = "What excercise are you doing?"
ex1.textAlignment = .center
ex1.translatesAutoresizingMaskIntoConstraints = false
return ex1
}()
//Exercise 1 title TextField
let excercise1RepsTextField: UITextField = {
let ex1Reps = UITextField()
ex1Reps.placeholder = "Reps"
ex1Reps.textAlignment = .center
ex1Reps.translatesAutoresizingMaskIntoConstraints = false
return ex1Reps
}()
//Exercise 1 title TextField
let excercise1SetsTextField: UITextField = {
let ex1Sets = UITextField()
ex1Sets.placeholder = "Sets"
ex1Sets.textAlignment = .center
ex1Sets.translatesAutoresizingMaskIntoConstraints = false
return ex1Sets
}()
override func viewDidLoad() {
super.viewDidLoad()
//making scroll view
let screensize: CGRect = UIScreen.main.bounds
let screenWidth = screensize.width
let screenHeight = screensize.height
var scrollView: UIScrollView!
scrollView = UIScrollView(frame: CGRect(x: 0, y: 120, width: screenWidth, height: screenHeight))
scrollView.contentSize = CGSize(width: screenWidth, height: 2000)
view.addSubview(scrollView)
//setting up how view looks-----
view.backgroundColor = .white
//top buttons
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDone))
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(handleCancel))
//view elements in scrollview
//Workout Title label
let workoutTitleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 25))
workoutTitleLabel.center = CGPoint(x: view.frame.width / 2 , y: view.frame.height / 20)
workoutTitleLabel.textAlignment = .center
workoutTitleLabel.text = "Workout Title"
workoutTitleLabel.font = UIFont.boldSystemFont(ofSize: 20)
scrollView.addSubview(workoutTitleLabel)
//workout title textfield
scrollView.addSubview(titleTextField)
titleTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleTextField.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
// textField.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
titleTextField.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor ,constant: -350),
titleTextField.widthAnchor.constraint(equalToConstant: view.frame.width - 64)
])
titleTextField.becomeFirstResponder()
//excercise 1 the locked in title Label
let excersice1label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
excersice1label.center = CGPoint(x: view.frame.width / 2 , y: view.frame.height / 5)
excersice1label.textAlignment = .center
excersice1label.text = "Excercise 1"
excersice1label.font = UIFont.boldSystemFont(ofSize: 20)
scrollView.addSubview(excersice1label)
//excercise 1 title textField
scrollView.addSubview(excercise1TextField)
excercise1TextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
excercise1TextField.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
// textField.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
excercise1TextField.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor ,constant: -220),
excercise1TextField.widthAnchor.constraint(equalToConstant: view.frame.width - 64)
])
excercise1TextField.becomeFirstResponder()
//excercise 1 Reps textField
scrollView.addSubview(excercise1RepsTextField)
excercise1RepsTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
excercise1RepsTextField.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
// textField.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
excercise1RepsTextField.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor ,constant: -190),
excercise1RepsTextField.widthAnchor.constraint(equalToConstant: view.frame.width - 64)
])
excercise1RepsTextField.becomeFirstResponder()
//excercise 1 Sets textField
scrollView.addSubview(excercise1SetsTextField)
excercise1SetsTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
excercise1SetsTextField.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
// textField.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
excercise1SetsTextField.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor ,constant: -160),
excercise1SetsTextField.widthAnchor.constraint(equalToConstant: view.frame.width - 64)
])
excercise1SetsTextField.becomeFirstResponder()
}
//done button
#objc func handleDone(){
print("done")
//setting the uitextfields to the the contact
guard let fullname = titleTextField.text, titleTextField.hasText else {
print("handle error here")
return
}
/* guard might be needed if a certain element is left empty
guard let excerciseOne = excercise1TextField.text, excercise1TextField.hasText else {
print("handle error here")
let excerciseOne = "empty"
return
}
*/
//setting the user input elements to to the contact so it can be passed to vc1 and presented
let excerciseOne = excercise1TextField.text
let excerciseOneReps = excercise1RepsTextField.text
let excerciseOneSets = excercise1SetsTextField.text
let contact = Contact(fullname: fullname, excerciseOne: excerciseOne!, excerciseOneReps: excerciseOneReps!, excerciseOneSets: excerciseOneSets!)
delegate?.addContact(contact: contact)
print(contact.fullname)
}
//cancel button
#objc func handleCancel(){
self.dismiss(animated: true, completion: nil )
}
}

Get data from Realm List<>

So this is how my Realm classes looks like:
class Workout: Object {
#objc dynamic var date: Date? // Date I did the exercise
#objc dynamic var exercise: String? // Name of exercise, example; Bench Press, Squat, Deadlift etc..
// List of sets (to-many relationship)
var sets = List<Set>()
}
class Set: Object {
#objc dynamic var reps: Int = 0 // Number of reps for the each set
#objc dynamic var kg: Double = 0.0 // Amount of weight/kg for each set
#objc dynamic var notes: String? // Notes for each set
// Define an inverse relationship to be able to access your parent workout for a particular set (if needed)
var parentWorkout = LinkingObjects(fromType: Workout.self, property: "sets")
convenience init(numReps: Int, weight: Double, aNote: String) {
self.init()
self.reps = numReps
self.kg = weight
self.notes = aNote
}
}
I want to get the weight(kg) and number of reps for each set, from the last time I did this exercise. So I am trying something like this. To query a workout with exercise "Text Exercise2", I do this:
func queryFromRealm() {
let realm = try! Realm()
let getExercises = realm.objects(Workout.self).filter("exercise = 'Text Exercise2'").sorted(byKeyPath: "date",ascending: false)
print(getExercises.last!)
myTableView.reloadData()
}
That gives me an output of this:
Workout {
date = 2019-11-24 00:33:21 +0000;
exercise = Text Exercise2;
sets = List<Set> <0x600003fcdb90> (
[0] Set {
reps = 12;
kg = 7;
notes = light workout;
},
[1] Set {
reps = 12;
kg = 8;
notes = medium workout;
},
[2] Set {
reps = 12;
kg = 9;
notes = heavy workout;
}
);
}
This is how I am passing data from mainVC to ExerciseCreatorViewController:
var selectedWorkout = Workout()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toEditExerciseCreatorSegue" {
if let nextVC = segue.destination as? ExerciseCreatorViewController {
nextVC.selectedWorkout = self.selectedWorkout
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
selectedWorkout = resultFromDate[indexPath.row]
performSegue(withIdentifier: "toEditExerciseCreatorSegue", sender: self)
}
From now on, I want to add these into at UITableView, where each row contains the kg and number of reps. How can I do this? Should I change my setup somehow? Looking forward for your help.
EDIT - Uploaded the whole ExerciseCreatorViewController.swift file:
import UIKit
import RealmSwift
class ExerciseCreatorViewController: UIViewController, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
let menuButtonSize: CGSize = CGSize(width: 44, height: 44)
let actionButtonSize: CGSize = CGSize(width: 44, height: 66)
let screenSize: CGRect = UIScreen.main.bounds
var resultFromDate:[Workout] = []
var selectedWorkout = Workout()
#IBOutlet var exerciseNameTextField: UITextField!
let exerciseName = UILabel()
#IBOutlet var navView: UIView!
let navLabel = UILabel()
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
exerciseNameTextField.delegate = self
// Get main screen bounds
let screenSize: CGRect = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
//self.view.backgroundColor = UIColor(red: 32/255, green: 32/255, blue: 32/255, alpha: 1.0)
self.view.backgroundColor = UIColor.white
// Navigation View
navView.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: 66)
navView.frame.origin.y = 28
//navLabel.backgroundColor = UIColor(red: 32/255, green: 32/255, blue: 32/255, alpha: 1.0)
navLabel.backgroundColor = UIColor.white
//navView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
self.view.addSubview(navView)
// Navigation Label
self.navView.addSubview(navLabel)
navLabel.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: 66)
navLabel.textAlignment = .center
navLabel.font = UIFont.boldSystemFont(ofSize: 20)
navLabel.textColor = UIColor.black
//navLabel.text = "Workout"
navLabel.text = "Log workout"
navLabel.adjustsFontSizeToFitWidth = true
navLabel.isUserInteractionEnabled = true
self.view.backgroundColor = UIColor.white
configureButtons()
setupFooter()
self.hideKeyboard()
print("Printing ... ... ...")
print(selectedWorkout)
}
func setupFooter() {
let customView = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: 50))
customView.backgroundColor = UIColor.lightGray
let button = UIButton(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: 50))
button.setTitle("Add Set", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
customView.addSubview(button)
tableView.tableFooterView = customView
}
#objc func buttonAction(_ sender: UIButton!) { // Add new set/row in tableView
print("Button tapped")
let a = Array(stride(from: 1, through: 50, by: 1)) // Haven't tested yet. Add new set in array
setsArray.append("a")
// Update Table Data
tableView.beginUpdates()
tableView.insertRows(at: [
(NSIndexPath(row: setsArray.count-1, section: 0) as IndexPath)
], with: .automatic)
tableView.endUpdates()
}
func configureButtons() {
confiureCloseButton()
confiureSaveButton()
confiureClearButton()
}
func confiureCloseButton() {
//let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: menuButtonSize))
button.setImage(UIImage(named: "icon_close"), for: UIControl.State.normal)
button.setImage(UIImage(named: "icon_close")?.alpha(0.5), for: UIControl.State.highlighted)
button.center = CGPoint(x: 50, y: 61)
button.layer.cornerRadius = button.frame.size.width/2
button.layer.zPosition = 1
button.addTarget(self, action: #selector(closeButtonAction), for: .touchUpInside)
button.setTitleColor(.black, for: UIControl.State.normal)
button.backgroundColor = UIColor.red
button.isUserInteractionEnabled = true
self.view.addSubview(button)
}
#objc func closeButtonAction(sender: UIButton!) {
self.dismiss(animated: true, completion: nil)
}
func confiureSaveButton() {
let button = UIButton(frame: CGRect(origin: CGPoint(x: screenSize.width-44-8, y: 28), size: actionButtonSize))
button.setTitle("Save", for: UIControl.State.normal)
//button.backgroundColor = UIColor.blue
button.layer.zPosition = 1
button.addTarget(self, action: #selector(saveButtonAction), for: .touchUpInside)
button.setTitleColor(.black, for: UIControl.State.normal)
button.isUserInteractionEnabled = true
self.view.addSubview(button)
}
#objc func saveButtonAction(sender: UIButton!) {
saveWorkout()
}
func confiureClearButton() {
let button = UIButton(frame: CGRect(origin: CGPoint(x: screenSize.width-44-8-44-8, y: 28), size: actionButtonSize))
button.setTitle("Clear", for: UIControl.State.normal)
//button.backgroundColor = UIColor.red
button.layer.zPosition = 1
button.addTarget(self, action: #selector(clearButtonAction), for: .touchUpInside)
button.isUserInteractionEnabled = true
button.setTitleColor(.black, for: UIControl.State.normal)
self.view.addSubview(button)
}
#objc func clearButtonAction(sender: UIButton!) {
clearWorkout()
}
func clearWorkout() { // Clear workout exercise
print("clear")
}
let nowDate = Date()
var setsArray = [String]()
var previousArray = [String]()
var kgArray = [String]()
var repsArray = [String]()
var notesArray = [String]()
func saveWorkout() { // Save workout exercise
if setsArray.isEmpty {//if !setsArray.isEmpty {
print("Saving workout...")
/*let realm = try! Realm()
let myWorkout = Workout()
myWorkout.date = nowDate
myWorkout.exercise = "Text Exercise"
let aSet0 = Set(numReps: 12, weight: 11.5, aNote: "light workout")
let aSet1 = Set(numReps: 12, weight: 12.5, aNote: "medium workout")
let aSet2 = Set(numReps: 12, weight: 21.0, aNote: "heavy workout")
myWorkout.sets.append(objectsIn: [aSet0, aSet1, aSet2] )
try! realm.write {
realm.add(myWorkout)
print("Saved!")
}*/
} else {
// create the alert
let alert = UIAlertController(title: nil, message: "Please add any exercise before saving!", preferredStyle: UIAlertController.Style.alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return setsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ExerciseCreatorCell
let realm = try! Realm()
let getExercises = realm.objects(Workout.self).filter("exercise = 'Text Exercise2'").sorted(byKeyPath: "date",ascending: false)
print(getExercises.last!)
let myWorkout = Workout()
// Find the Workout instance "Bench Press"
let benchPressWorkout = realm.objects(Workout.self).filter("exercise = 'Text Exercise2'").sorted(byKeyPath: "date",ascending: false)
// Access the sets for that instance
let sets = benchPressWorkout[0].sets
for i in sets.indices {
let set0 = sets[i]
// Access reps and kg for set 0
let reps0 = set0.reps
let kg0 = set0.kg
// and so on ...
print([kg0]) // number of KG
print([reps0]) // number of reps
cell.previousTextField.text = "\(kg0) x \(reps0)"
}
cell.setLabel.text = "\(indexPath.row + 1)"
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("tapped")
}
}
EDIT: I have three objects as #rs7 suggested earlier:
Workout.swift:
#objc dynamic var id = 0
#objc dynamic var date: Date?
// List of exercises (to-many relationship)
var exercises = List<Exercise>()
override static func primaryKey() -> String? {
return "id"
}
Exercise.swift
class Exercise: Object {
#objc dynamic var name: String?
// List of sets (to-many relationship)
var sets = List<Set>()
var parentWorkout = LinkingObjects(fromType: Workout.self, property: "exercises")
}
Set.swift
class Set: Object {
#objc dynamic var reps: Int = 0
#objc dynamic var kg: Double = 0.0
#objc dynamic var notes: String?
// Define an inverse relationship to be able to access your parent workout for a particular set (if needed)
var parentExercise = LinkingObjects(fromType: Exercise.self, property: "sets")
convenience init(numReps: Int, weight: Double, aNote: String) {
self.init()
self.reps = numReps
self.kg = weight
self.notes = aNote
}
}
I'll give you the answer anyway, but don't forget to fix your code.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// number of rows = number of sets (not counting the header row)
return selectedExercise.sets.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ExerciseCreatorCell
let currentSet = selectedExercise.sets[indexPath.row]
cell.setNumberLabel.text = "\(indexPath.row)"
cell.kgLabel.text = "\(currentSet.kg)"
cell.repsLabel.text = "\(currentSet.reps)"
return cell
}

Presenting view controller without identifier programmatically results in black screen

I have a viewcontroller that allows users to create accounts. Once the account is created, I am trying to instantiate a new viewcontroller called intermediate vc programmatically. I have searched through stack overflow and I can't seem to find any relevant answers. I'm not able to set an identifier for the viewcontroller since it's not part of storyboard.
I created a button in my createAccountController that should result in the transition to the next screen.
here is the relevant code for my createAccountController
let testButton: UIButton = UIButton()
override func viewDidLoad() {
view.addSubview(testButton)
testButton.backgroundColor = UIColor.black
testButton.setTitle("Hi", for: .normal)
testButton.translatesAutoresizingMaskIntoConstraints = false
testButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
testButton.centerYAnchor.constraint(equalTo: usernameName.centerYAnchor, constant: 20).isActive = true
testButton.addTarget(self, action: #selector(moveToIntermediate), for: .touchUpInside)
}
intermediateViewController is the viewcontroller class I created within my createAccountController, and then function below is the function in my createAccountController.
#objc func moveToIntermediate() {
let interMediate = intermediateViewController()
interMediate.modalTransitionStyle = .crossDissolve
interMediate.view.layer.speed = 0.2
self.present(interMediate, animated: true, completion: nil)
}
Here is the intermediateViewController code
class intermediateViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let floatingButton: UIButton = UIButton()
let categoryTableView: UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
setTableView()
setNavBar()
setFloatingButton()
//when view starts
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
//you can dispose of stuff here
}
func setTableView() {
categoryTableView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(categoryTableView)
categoryTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
categoryTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
categoryTableView.topAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true
categoryTableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = categoryTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
return cell
}
func setNavBar() {
self.navigationController?.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(moveToNext))
self.navigationController?.navigationBar.backgroundColor = UIColor(red:0.22, green:0.46, blue:0.82, alpha:1.0)
self.navigationController?.navigationBar.topItem?.title = "Categories"
self.title = "some title"
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
self.navigationController?.navigationBar.isTranslucent = false
//self.transitioningDelegate
//self.translatesAutoresizingMaskIntoConstraints = false
}
#IBAction func moveToNext() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let moveToTabBar = storyboard.instantiateViewController(withIdentifier: "TabBarViewController")
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = moveToTabBar
}
func setFloatingButton() {
floatingButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(floatingButton)
floatingButton.frame.size = CGSize(width: 150, height: 50)
floatingButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
floatingButton.centerYAnchor.constraint(equalTo: self.view.bottomAnchor, constant: floatingButton.frame.size.height).isActive = true
floatingButton.layer.cornerRadius = floatingButton.frame.size.height/2
floatingButton.backgroundColor = UIColor.black
floatingButton.setTitle("Done", for: .normal)
floatingButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
floatingButton.setTitleColor(UIColor.white, for: .normal)
}
}
In your constraints:
categoryTableView.topAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true
categoryTableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
top and bottom anchors equal self.view.bottomAnchor
Update: just saw that Axazeano had already found the answer.
You made a small typo in a constraint:
categoryTableView.topAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
Corrected version:
categoryTableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
Also: do not forget to set the viewController as the dataSource of your tableView.

Resources