Index is only up to 1 in swift tableView - ios

This code is SettingTableViewCell
enter image description here
class SettingTableViewCell: UITableViewCell {
static let identifier = "SettingTableViewCell"
private let iconContainer = UIView().then {
$0.clipsToBounds = true
$0.layer.cornerRadius = 10
$0.layer.masksToBounds = true
}
private let iconImageView = UIImageView().then {
$0.tintColor = .white
$0.contentMode = .scaleAspectFit
}
private let label = UILabel().then {
$0.numberOfLines = 1
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
[label, iconImageView, iconContainer].forEach { contentView.addSubview($0) }
contentView.clipsToBounds = true
accessoryType = .disclosureIndicator
}
required init?(coder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
let size: CGFloat = contentView.frame.size.height - 12
iconContainer.frame = CGRect(x: 10, y: 6, width: size, height: size)
let imageSize: CGFloat = size/1.5
iconImageView.frame = CGRect(x: (size-imageSize)/2, y: (size-imageSize)/2, width: imageSize, height: imageSize)
iconImageView.center = iconContainer.center
label.frame = CGRect(x: 20 + iconContainer.frame.size.width, y: 0, width: contentView.frame.size.width - 20 - iconContainer.frame.size.width, height: contentView.frame.size.height)
}
override func prepareForReuse() {
super.prepareForReuse()
iconImageView.image = nil
label.text = nil
iconContainer.backgroundColor = nil
}
public func configure(with model: SettingOption) {
label.text = model.title
iconImageView.image = model.icon
iconContainer.backgroundColor = model.iconBackgroundColor
}
}
this code is SettingViewController
import UIKit
struct Section {
let title: String
let options: [SettingsOptionType]
}
enum SettingsOptionType {
case staticCell(model: SettingOption)
case switchCell(model: SettingsSwitchOption)
}
struct SettingsSwitchOption {
let title: String
let icon: UIImage?
let iconBackgroundColor: UIColor
let handler: (() -> Void)
var isOn: Bool
}
struct SettingOption {
let title: String
let icon: UIImage?
let iconBackgroundColor: UIColor
let handler: (() -> Void)
}
final class SettingViewController: UIViewController {
private lazy var presenter = SettingPresenter(viewController: self)
private let tableView: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.register(SettingTableViewCell.self, forCellReuseIdentifier: SettingTableViewCell.identifier)
table.register(SwitchTableViewCell.self, forCellReuseIdentifier: SwitchTableViewCell.identifier)
return table
}()
var models = [Section]()
override func viewDidLoad() {
super.viewDidLoad()
configure()
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.frame = view.bounds
}
func configure() {
//스위치 관련
// models.append(Section(title: "", options: [
// .switchCell(model: SettingsSwitchOption(title: "Airplane Mode", icon: UIImage(systemName: "airplane"), iconBackgroundColor: .systemRed, handler: {
//
// }, isOn: true))
// ]))
// models.append(Section(title: "개인정보", options: [
// .staticCell(model: SettingOption(title: "gmail", icon: UIImage(systemName: ""), iconBackgroundColor: .green, handler: {
// print("asdfasdf")
// })),
// .staticCell(model: SettingOption(title: "아이디 정보", icon: UIImage(systemName: ""), iconBackgroundColor: .green, handler: {
// print("sdfgsdfgsdfg")
// }))
// ]))
models.append(Section(title: "개인정보", options: [
.staticCell(model: SettingOption(title: "gmail 정보", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
let idVC = IdInformationViewController()
self.navigationController?.pushViewController(idVC, animated: true)
print("클릭함")
}),
.staticCell(model: SettingOption(title: "아이디 정보", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
print("하하")
})
]))
//
models.append(Section(title: "보안", options: [
.staticCell(model: SettingOption(title: "앱 비밀번호", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
}),
.staticCell(model: SettingOption(title: "침입 시도", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
}),
.staticCell(model: SettingOption(title: "앱 열기 추적", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
print("asdfasdf r")
})
]))
models.append(Section(title: "앱", options: [
.staticCell(model: SettingOption(title: "앱 아이콘 변경", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
}),
.staticCell(model: SettingOption(title: "앱 테마 변경", icon: UIImage(systemName: "bluetooth"), iconBackgroundColor: .gray) {
})
]))
models.append(Section(title: "", options: [
.staticCell(model: SettingOption(title: "지원", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
}),
.staticCell(model: SettingOption(title: "버그 보고하기", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
})
]))
models.append(Section(title: "", options: [
.staticCell(model: SettingOption(title: "피드백", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
})
]))
models.append(Section(title: "", options: [
.staticCell(model: SettingOption(title: "앱 공유", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
}),
.staticCell(model: SettingOption(title: "개인정보 처리 방침", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
}),
.staticCell(model: SettingOption(title: "이용 약관", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
})
]))
models.append(Section(title: "", options: [
.staticCell(model: SettingOption(title: "사용 방법", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
}),
.staticCell(model: SettingOption(title: "개발자 정보", icon: UIImage(systemName: ""), iconBackgroundColor: .gray) {
})
]))
}
}
extension SettingViewController: SettingProtocol {
}
extension SettingViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let section = models[section]
return section.title
}
func numberOfSections(in tableView: UITableView) -> Int {
print("numberOfSections \(models.count)")
return models.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(" numberOfRowsInSection \(models[section].options.count)")
return models[section].options.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.section].options[indexPath.row]
print("model: \(model)")
switch model.self {
case .staticCell(let model):
guard let cell = tableView.dequeueReusableCell(
withIdentifier: SettingTableViewCell.identifier,
for: indexPath) as? SettingTableViewCell else {
return UITableViewCell()
}
cell.configure(with: model)
return cell
case .switchCell(let model):
guard let cell = tableView.dequeueReusableCell(
withIdentifier: SwitchTableViewCell.identifier,
for: indexPath) as? SwitchTableViewCell else {
return UITableViewCell()
}
cell.configure(with: model)
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let type = models[indexPath.row].options[indexPath.row]
switch type.self {
case .staticCell(let model):
model.handler()
case .switchCell(let model):
model.handler()
}
}
}
enter image description here
enter image description here
enter image description here
this code is SwitchTableViewCell
import UIKit
import Then
class SwitchTableViewCell: UITableViewCell {
static let identifier = "SwitchTableViewCell"
private let iconContainer = UIView().then {
$0.clipsToBounds = true
$0.layer.cornerRadius = 10
$0.layer.masksToBounds = true
}
private let iconImageView = UIImageView().then {
$0.tintColor = .white
$0.contentMode = .scaleAspectFit
}
private let label = UILabel().then {
$0.numberOfLines = 1
}
private let mySwitch = UISwitch().then { $0.onTintColor = .systemBlue }
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
[label, iconContainer, mySwitch].forEach { contentView.addSubview($0) }
iconContainer.addSubview(iconImageView)
contentView.clipsToBounds = true
accessoryType = .none
}
required init?(coder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
let size: CGFloat = contentView.frame.size.height - 12
iconContainer.frame = CGRect(x: 10, y: 6, width: size, height: size)
let imageSize: CGFloat = size/1.5
iconImageView.frame = CGRect(x: (size-imageSize)/2, y: (size-imageSize)/2, width: imageSize, height: imageSize)
iconImageView.center = iconContainer.center
mySwitch.sizeToFit()
mySwitch.frame = CGRect(x: contentView.frame.size.width - mySwitch.frame.size.width - 10, y: (contentView.frame.size.height - mySwitch.frame.size.height)/2, width: mySwitch.frame.size.width, height: mySwitch.frame.size.height)
label.frame = CGRect(x: 20 + iconContainer.frame.size.width, y: 0, width: contentView.frame.size.width - 20 - iconContainer.frame.size.width, height: contentView.frame.size.height)
}
override func prepareForReuse() {
super.prepareForReuse()
iconImageView.image = nil
label.text = nil
iconContainer.backgroundColor = nil
mySwitch.isOn = false
}
public func configure(with model: SettingsSwitchOption) {
label.text = model.title
iconImageView.image = model.icon
iconContainer.backgroundColor = model.iconBackgroundColor
mySwitch.isOn = model.isOn
}
}
enter image description here
I'm setting up iOS, but I don't know if it's because tableView overlaps, but if indexPath is only up to 1, and indexPath.row is 0, the 0th of all cells is the same as the first declared result.
To give you a little bit more detail, the function configure automatically gives each model an interval when the model comes in, so it fits the cell price index.
enter image description here
enter image description here
I want the index of all cells to be different, so if I put the value in the header and click on it, the events in the header will be executed.

Related

How to implement the 'datepicker' correctly using swift when adding the multiple entries to server?

**Aim: ** To show latest apple calendar type of date picker(inline) in app when user wants to select the date.How it should look
Scenario:**
The user can input multiple entries (maximum 3) the exercise(suryanamaskar). These multiple entries has two functions
1.date-picker and
2.number entry(number of 'suryanamaskar')
When user clicks on the date-picker button. the date picker shows up (.inline). Each entry fetches the post API call that saves the 'suryanamaskar' count entry on server.
What is the issue: I tried to implement the date-picker using and few UI frames : .inline
I replaced the date-picker completely and implemented ".wheels" that works fine.
The datepicker is show up correctly first time. But when the user tried to add other entry or press the date entry buttton . The datepicker gets cut and is not properly visible. This is how it looks when user clicks on datepicker button second time.
What my code looks like:
import UIKit
import SwiftyJSON
import Alamofire
struct surynamaskarStruct {
var date: String
var count: String
}
class AddSuryaNamskarCountVC: UIViewController, UITextFieldDelegate {
var surynamaskarData: [surynamaskarStruct] = [
surynamaskarStruct(date: "Date", count: "0"),
surynamaskarStruct(date: "Date", count: "0"),
surynamaskarStruct(date: "Date", count: "0")
]
#IBOutlet var tableView: UITableView!
#IBOutlet weak var lblMember: UILabel!
#IBOutlet weak var btnMember: UIButton!
#IBOutlet weak var familyMemberHeightConstraint: NSLayoutConstraint!
var viewPicker : UIView!
let datePicker = UIDatePicker()
var btnIndex : Int!
var strMemberId : String!
var dicMember = [[String:Any]]()
lazy var loader : UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(style: .large)
indicator.hidesWhenStopped = true
return indicator
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if self.dicMember.count > 0 {
self.strMemberId = self.dicMember[0]["id"] as? String
self.lblMember.text = self.dicMember[0]["name"] as? String
self.familyMemberHeightConstraint.constant = 90
} else {
self.familyMemberHeightConstraint.constant = 0
}
view.addSubview(loader)
loader.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
loader.centerXAnchor.constraint(equalTo: view.centerXAnchor),
loader.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
override func viewWillAppear(_ animated: Bool) {
navigationBarDesign(txt_title: "Record Suryanamskar", showbtn: "back")
}
#IBAction func onMemberClick(_ sender: UIButton) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "PickerTableViewWithSearchViewController") as! PickerTableViewWithSearchViewController
vc.selectedItemCompletion = {dict in
self.lblMember.text = dict["name"] as? String
self.strMemberId = dict["id"] as? String
}
vc.dataSource = dicMember
vc.strNavigationTitle = "Family Member"
vc.modalPresentationStyle = UIModalPresentationStyle.fullScreen
self.present(vc, animated: true, completion: nil)
}
#IBAction func onCancelClick(_ sender: UIButton) {
self.dismiss(animated: true)
}
#IBAction func onSaveClick(_ sender: UIButton) {
var arrData = [[String:Any]]()
for arr in surynamaskarData {
if arr.date != "Date" && arr.count != "0" {
var dict = [String : Any]()
dict["date"] = arr.date
dict["count"] = arr.count
arrData.append(dict)
}
}
if arrData.count > 0 {
self.saveSuryaNamskarCountDataAPI(arrData)
////static controller to directly update the suryanamaskar chart instead of going back and then getting updated suryanamaskar
let alertController = UIAlertController(title:APP.title, message: "Suryanamaskar has been saved successfully!", preferredStyle:.alert)
let Action = UIAlertAction.init(title: "Ok", style: .default) { (UIAlertAction) in
// Write Your code Here
let vc = self.storyboard?.instantiateViewController(withIdentifier: "SuryaNamskarVC") as! SuryaNamskarVC
vc.modalPresentationStyle = UIModalPresentationStyle.fullScreen
// vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(vc, animated: true, completion: nil)
}
alertController.addAction(Action)
self.present(alertController, animated: true, completion: nil)
//// Till here for alert controller. if not implemented, updated suryanamaskar will happen only once user goes back and comes back to suryanamaskarVC
}
// else {
// showAlert(title: APP.title, message: "Please select date and count before Save")
// }
}
// MARK: - DatePicker functions
func showDatePicker(){
viewPicker = UIView(frame: CGRect(x: 0.0, y: self.view.frame.height - 380, width: self.view.frame.width, height: 380))
viewPicker.backgroundColor = UIColor.white
viewPicker.clipsToBounds = true
// Posiiton date picket within a view
datePicker.frame = CGRect(x: 10, y: 50, width: self.view.frame.width, height: 200)
datePicker.datePickerMode = .date
datePicker.minimumDate = Calendar.current.date(byAdding: .month, value: -2, to: Date())
datePicker.maximumDate = Calendar.current.date(byAdding: .year, value: 0, to: Date())
datePicker.backgroundColor = UIColor.white
if #available(iOS 14.0, *) {
datePicker.preferredDatePickerStyle = .inline
} else {
if #available(iOS 13.4, *) {
datePicker.preferredDatePickerStyle = .wheels
} else {
// Fallback on earlier versions
}
// Fallback on earlier versions
}
// Add an event to call onDidChangeDate function when value is changed.
datePicker.addTarget(self, action: #selector(AddMemberStep1VC.datePickerValueChanged(_:)), for: .valueChanged)
datePicker.center.x = self.view.center.x
//ToolBar
var toolbar = UIToolbar();
toolbar.sizeToFit()
toolbar = UIToolbar(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: 50))
let doneButton = UIBarButtonItem(title: "done".localized, style: .plain, target: self, action: #selector(donedatePicker));
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "cancel".localized, style: .plain, target: self, action: #selector(cancelDatePicker));
toolbar.setItems([doneButton,spaceButton,cancelButton], animated: false)
// txtDate.inputAccessoryView = toolbar
// txtDate.inputView = datePicker
self.viewPicker.addSubview(toolbar)
self.viewPicker.addSubview(datePicker)
self.viewPicker.clipsToBounds = true
self.view.addSubview(self.viewPicker)
}
#objc func datePickerValueChanged(_ sender: UIDatePicker){
// Create date formatter
let dateFormatter: DateFormatter = DateFormatter()
// Set date format
dateFormatter.dateFormat = "dd/MM/yyyy"
// Apply date format
let _: String = dateFormatter.string(from: sender.date)
// print("Selected value \(selectedDate)")
}
#objc func donedatePicker(){
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy"
let strDate = formatter.string(from: datePicker.date)
surynamaskarData[btnIndex].date = strDate
DispatchQueue.main.async {
self.tableView.reloadData()
}
self.viewPicker.isHidden = true
}
#objc func cancelDatePicker(){
self.viewPicker.isHidden = true
}
#objc func onAddMoreClicked() {
print("AddMore Button Pressed")
for i in surynamaskarData {
if i.date == "Date" || i.count == "0" || i.count == "" {
showAlert(title: APP.title, message: "Please select date and count before AddMore")
return
}
}
surynamaskarData.append(surynamaskarStruct(date: "Date", count: "0"))
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
#objc func onDateClick(_ sender: UIButton) {
btnIndex = sender.tag
view.endEditing(true)
if self.viewPicker == nil {
self.showDatePicker()
} else {
if self.viewPicker.isHidden == true {
self.showDatePicker()
} else {
self.viewPicker.isHidden = true
}
}
}
#objc func onDeleteClick(_ sender: UIButton) {
surynamaskarData.remove(at: sender.tag)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if self.viewPicker != nil {
if self.viewPicker.isHidden == false {
self.viewPicker.isHidden = false
}
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text == "" {
textField.text = "0"
}
surynamaskarData[textField.tag].count = textField.text ?? "0"
}
func json(from object:Any) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
return nil
}
return String(data: data, encoding: String.Encoding.utf8)
}
func saveSuryaNamskarCountDataAPI(_ data : [[String:Any]]) {
var parameters: [String: Any] = [:]
parameters["surynamaskar"] = self.json(from: data)
parameters["member_id"] = self.strMemberId // _appDelegator.dicDataProfile![0]["member_id"] as? String
print(parameters)
// APIUrl.save_suryanamaskar
//live API only
//"https://myhss.org.uk/api/v1/suryanamaskar/save_suryanamaskar_count"
loader.startAnimating()
let url = URL(string: APIUrl.save_suryanamaskar)!
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.validate()
.responseJSON { response in
self.loader.stopAnimating()
switch response.result {
case .success(let response):
print(response)
let jsonData = JSON(response)
if let status = jsonData["status"].int
{
if status == 1
{
let strMessage : String = jsonData["message"].rawValue as! String
print(strMessage)
// create the alert
let alert = UIAlertController(title: APP.title, message: strMessage, preferredStyle: UIAlertController.Style.alert)
// add an action (button)
let ok = UIAlertAction(title: "Ok".localized, style: .default, handler: { action in
DispatchQueue.main.async {
self.dismiss(animated: true)
}
})
alert.addAction(ok)
// show the alert
self.present(alert, animated: true, completion: nil)
} else {
if let strError = jsonData["message"].string {
showAlert(title: APP.title, message: strError)
}
}
}
case .failure(let error):
print(error.localizedDescription)
showAlert(title: APP.title, message: error.localizedDescription)
}
}
}
}
extension AddSuryaNamskarCountVC : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return surynamaskarData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: AddSuryaNamskarCountTVCell.cellIdentifier) as! AddSuryaNamskarCountTVCell
cell.btnDate.tag = indexPath.row
cell.btnDate.addTarget(self, action: #selector(self.onDateClick(_:)), for: .touchUpInside)
cell.btnCancel.tag = indexPath.row
cell.btnCancel.addTarget(self, action: #selector(self.onDeleteClick(_:)), for: .touchUpInside)
cell.lblDate.text = surynamaskarData[indexPath.row].date
cell.txtCount.text = surynamaskarData[indexPath.row].count
cell.txtCount.tag = indexPath.row
cell.txtCount.delegate = self
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.0
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let viewFooter = UIView()
viewFooter.backgroundColor = UIColor.systemGray5
let size = tableView.frame.size
let addMoreButton = UIButton()
addMoreButton.setTitle("+ Add More", for: .normal)
addMoreButton.setTitleColor(Colors.txtAppDarkColor, for: .normal)
addMoreButton.frame = CGRect(x: 0, y: 0, width: size.width, height: 50)
addMoreButton.addTarget(self, action: #selector(onAddMoreClicked), for: .touchUpInside)
viewFooter.addSubview(addMoreButton)
return viewFooter
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 50
}
}
Changing the Co-ordinates works:
Following co-orinates fixes the calender view for this issue.
func showDatePicker(){
viewPicker = UIView(frame: CGRect(x: 0.0, y: self.view.frame.height - 500, width: self.view.frame.width, height: 500))
viewPicker.backgroundColor = UIColor.white
viewPicker.clipsToBounds = true
// Posiiton date picket within a view
datePicker.frame = CGRect(x: 10, y: 50, width: self.view.frame.width, height: 500)
datePicker.datePickerMode = .date
datePicker.minimumDate = Calendar.current.date(byAdding: .month, value: -2, to: Date())
datePicker.maximumDate = Calendar.current.date(byAdding: .year, value: 0, to: Date())
datePicker.backgroundColor = UIColor.white
if #available(iOS 14.0, *) {
datePicker.preferredDatePickerStyle = .inline
} else {
if #available(iOS 13.4, *) {
datePicker.preferredDatePickerStyle = .wheels
} else {
// Fallback on earlier versions
}
// Fallback on earlier versions
}
// Add an event to call onDidChangeDate function when value is changed.
datePicker.addTarget(self, action: #selector(AddMemberStep1VC.datePickerValueChanged(_:)), for: .valueChanged)
datePicker.center.x = self.view.center.x
//ToolBar
var toolbar = UIToolbar();
toolbar.sizeToFit()
toolbar = UIToolbar(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: 50))
let doneButton = UIBarButtonItem(title: "done".localized, style: .plain, target: self, action: #selector(donedatePicker));
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "cancel".localized, style: .plain, target: self, action: #selector(cancelDatePicker));
toolbar.setItems([doneButton,spaceButton,cancelButton], animated: false)
self.viewPicker.addSubview(toolbar)
self.viewPicker.addSubview(datePicker)
self.viewPicker.clipsToBounds = true
self.view.addSubview(self.viewPicker)
}

How to display the image from tableViewCell downloaded from URL using SDWebImage in the detailViewController?

My app downloads images from firebase and displays them in the tableViewController cells, also I need the AudioPlayerViewController to display exactly same image of the selected row/cell. For now in anyway it displays only the last downloaded image. I tried to save downloaded images into array but it always threw errors like cannot assign UIImage to [UIImage] or something like this. Hope to your help friends.
import UIKit
import AVKit
import AVFoundation
import FirebaseFirestore
import Combine
import SDWebImage
class ListOfAudioLessonsTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var table: UITableView!
let placeHolderImage = UIImage(named: "placeHolderImage")
private var viewModel = AudiosViewModel()
private var cancellable: AnyCancellable?
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.fetchData()
self.title = "Audio Lessons"
let nib = UINib(nibName: "AudioCustomTableViewCell", bundle: nil)
table.register(nib, forCellReuseIdentifier: "audioCustomCell")
table.delegate = self
table.dataSource = self
cancellable = viewModel.$audios.sink { _ in
DispatchQueue.main.async{
self.table.reloadData()
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("audios count = ", viewModel.audios.count)
return viewModel.audios.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "audioCustomCell", for: indexPath) as? AudioCustomTableViewCell
let song = viewModel.audios[indexPath.row]
let imageURL = song.audioImageName
cell?.audioImage.sd_imageIndicator = SDWebImageActivityIndicator.gray
cell?.audioImage.sd_setImage(with: URL(string: imageURL),
placeholderImage: placeHolderImage,
options: SDWebImageOptions.highPriority,
context: nil,
progress: nil,
completed: { downloadedImage, downloadException, cacheType, downloadURL in
if let downloadException = downloadException {
print("error downloading the image: \(downloadException.localizedDescription)")
} else {
print("successfuly downloaded the image: \(String(describing: downloadURL?.absoluteString))")
}
self.viewModel.image = cell?.audioImage.image
})
tableView.tableFooterView = UIView()
cell?.textLabel?.font = UIFont(name: "Helvetica-Bold", size: 14)
cell?.detailTextLabel?.font = UIFont(name: "Helvetica", size: 12)
cell?.commonInit(song.albumName, song.name, viewModel.image)
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let position = indexPath.row
guard let vc = storyboard?.instantiateViewController(identifier: "AudioPlayer") as? AudioPlayerViewController else {
return
}
vc.mainImage = viewModel.image
vc.paragraphs = viewModel.audios
vc.position = position
present(vc, animated: true)
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.contentView.backgroundColor = UIColor(named: "AudioLessonsHighlighted")
cell.textLabel?.highlightedTextColor = UIColor(named: "textHighlighted")
cell.detailTextLabel?.highlightedTextColor = UIColor(named: "textHighlighted")
}
}
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.contentView.backgroundColor = nil
}
}
}
This is the viewModel
import Foundation
import FirebaseFirestore
import SDWebImage
class AudiosViewModel: ObservableObject {
#Published var audios = [Audio]()
private var db = Firestore.firestore()
var image: UIImage?
func fetchData() {
db.collection("audios").addSnapshotListener { [self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No Documents")
return
}
self.audios = documents.map { (queryDocumentSnapshot) -> Audio in
let data = queryDocumentSnapshot.data()
let image = image
let name = data["name"] as? String ?? ""
let albumName = data["albumName"] as? String ?? ""
let audioImageName = data["audioImageName"] as? String ?? ""
let paragraphNumber = data["paragraphNumber"] as? String ?? ""
let trackURL = data["trackURL"] as? String ?? ""
return Audio(image: image, name: name, albumName: albumName, paragraphNumber: paragraphNumber, audioImageName: audioImageName, trackURL: trackURL)
}
}
}
}
this is the AudioPlayerViewController
import UIKit
import AVFoundation
import MediaPlayer
import AVKit
import Combine
class AudioPlayerViewController: UIViewController {
// private var viewModel = AudiosViewModel()
public var position: Int = 0
public var paragraphs: [Audio] = []
public var mainImage = UIImage(named: "placeHolderImage")
#IBOutlet var holder: UIView!
var player: AVPlayer?
var playerItem: AVPlayerItem?
var isSeekInProgress = false
var chaseTime = CMTime.zero
fileprivate let seekDuration: Float64 = 15
var playerCurrentItemStatus: AVPlayerItem.Status = .unknown
// User Interface elements
private let albumImageView: UIImageView = {
let imageView = UIImageView()
imageView.layer.shadowOpacity = 0.3
imageView.layer.shadowRadius = 3
imageView.layer.shadowOffset = CGSize(width: 6, height: 6)
imageView.layer.shadowRadius = 8
imageView.contentMode = .scaleAspectFill
return imageView
}()
private let paragraphNumberLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 16, weight: .light)
label.numberOfLines = 0 // allow line wrap
label.textColor = UIColor(named: "PlayerColors")
return label
}()
private let albumNameLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 18, weight: .bold)
label.numberOfLines = 0 // allow line wrap
label.textColor = UIColor(named: "PlayerColors")
return label
}()
private let songNameLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 16, weight: .ultraLight)
label.numberOfLines = 0 // allow line wrap
label.textColor = UIColor(named: "PlayerColors")
return label
}()
private let elapsedTimeLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 12, weight: .light)
label.textColor = UIColor(named: "PlayerColors")
label.text = "00:00"
label.numberOfLines = 0
return label
}()
private let remainingTimeLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 12, weight: .light)
label.textColor = UIColor(named: "PlayerColors")
label.text = "00:00"
label.numberOfLines = 0
return label
}()
private let playbackSlider: UISlider = {
let v = UISlider()
v.addTarget(AudioPlayerViewController.self, action: #selector(progressScrubbed(_:)), for: .valueChanged)
v.minimumTrackTintColor = UIColor.lightGray
v.maximumTrackTintColor = UIColor.darkGray
v.thumbTintColor = UIColor(named: "PlayerColors")
v.minimumValue = 0
v.isContinuous = false
return v
}()
let playPauseButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGesture(gesture:)))
self.playbackSlider.addGestureRecognizer(panGesture)
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSession.Category.playback)
}
catch{
print(error)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if holder.subviews.count == 0 {
configure()
}
}
func configure() {
// set up player
let song = paragraphs[position]
let url = URL(string: song.trackURL)
let playerItem: AVPlayerItem = AVPlayerItem(url: url!)
do {
try AVAudioSession.sharedInstance().setMode(.default)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard url != nil else {
print("urls string is nil")
return
}
player = AVPlayer(playerItem: playerItem)
let duration : CMTime = playerItem.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
remainingTimeLabel.text = self.stringFromTimeInterval(interval: seconds)
let currentDuration : CMTime = playerItem.currentTime()
let currentSeconds : Float64 = CMTimeGetSeconds(currentDuration)
elapsedTimeLabel.text = self.stringFromTimeInterval(interval: currentSeconds)
playbackSlider.maximumValue = Float(seconds)
player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if self.player!.currentItem?.status == .readyToPlay {
let time : Float64 = CMTimeGetSeconds(self.player!.currentTime());
self.playbackSlider.value = Float(time)
self.elapsedTimeLabel.text = self.stringFromTimeInterval(interval: time)
}
let playbackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp
if playbackLikelyToKeepUp == false{
print("IsBuffering")
self.playPauseButton.isHidden = true
} else {
// stop the activity indicator
print("Buffering completed")
self.playPauseButton.isHidden = false
}
}
playbackSlider.addTarget(self, action: #selector(AudioPlayerViewController.progressScrubbed(_:)), for: .valueChanged)
self.view.addSubview(playbackSlider)
//subroutine used to keep track of current location of time in audio file
guard let player = player else {
print("player is nil")
return
}
player.play()
}
catch {
print("error accured")
}
// set up user interface elements
//album cover
albumImageView.frame = CGRect(x: 20,
y: 20,
width: holder.frame.size.width - 40,
height: holder.frame.size.width - 40)
albumImageView.image = mainImage
holder.addSubview(albumImageView)
//Labels Song name, album, artist
albumNameLabel.frame = CGRect(x: 20,
y: holder.frame.size.height - 300,
width: holder.frame.size.width - 40,
height: 20)
paragraphNumberLabel.frame = CGRect(x: 20,
y: holder.frame.size.height - 280,
width: holder.frame.size.width-40,
height: 20)
songNameLabel.frame = CGRect(x: 20,
y: holder.frame.size.height - 260,
width: holder.frame.size.width-40,
height: 20)
playbackSlider.frame = CGRect(x: 20,
y: holder.frame.size.height - 235,
width: holder.frame.size.width-40,
height: 40)
elapsedTimeLabel.frame = CGRect(x: 25,
y: holder.frame.size.height - 200,
width: holder.frame.size.width-40,
height: 15)
remainingTimeLabel.frame = CGRect(x: holder.frame.size.width-60,
y: holder.frame.size.height - 200,
width: holder.frame.size.width-20,
height: 15)
songNameLabel.text = song.name
albumNameLabel.text = song.albumName
paragraphNumberLabel.text = song.paragraphNumber
holder.addSubview(songNameLabel)
holder.addSubview(albumNameLabel)
holder.addSubview(paragraphNumberLabel)
holder.addSubview(elapsedTimeLabel)
holder.addSubview(remainingTimeLabel)
//Player controls
let nextButton = UIButton()
let backButton = UIButton()
let seekForwardButton = UIButton()
let seekBackwardButton = UIButton()
//frames of buttons
playPauseButton.frame = CGRect(x: (holder.frame.size.width - 40) / 2.0,
y: holder.frame.size.height - 172.5,
width: 40,
height: 40)
nextButton.frame = CGRect(x: holder.frame.size.width - 70,
y: holder.frame.size.height - 162.5,
width: 30,
height: 20)
backButton.frame = CGRect(x: 70 - 30,
y: holder.frame.size.height - 162.5,
width: 30,
height: 20)
seekForwardButton.frame = CGRect(x: holder.frame.size.width - 140,
y: holder.frame.size.height - 167.5,
width: 30,
height: 30)
seekBackwardButton.frame = CGRect(x: 110,
y: holder.frame.size.height - 167.5,
width: 30,
height: 30)
let volumeView = MPVolumeView(frame: CGRect(x: 20,
y: holder.frame.size.height - 80,
width: holder.frame.size.width-40,
height: 30))
holder.addSubview(volumeView)
//actions of buttons
playPauseButton.addTarget(self, action: #selector(didTapPlayPauseButton), for: .touchUpInside)
backButton.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside)
nextButton.addTarget(self, action: #selector(didTapNextButton), for: .touchUpInside)
seekForwardButton.addTarget(self, action: #selector(seekForwardButtonTapped), for: .touchUpInside)
seekBackwardButton.addTarget(self, action: #selector(seekBackwardButtonTapped), for: .touchUpInside)
//styling of buttons
playPauseButton.setBackgroundImage(UIImage(systemName: "pause.fill"), for: .normal)
nextButton.setBackgroundImage(UIImage(systemName: "forward.fill"), for: .normal)
backButton.setBackgroundImage(UIImage(systemName: "backward.fill"), for: .normal)
seekForwardButton.setBackgroundImage(UIImage(systemName: "goforward.15"), for: .normal)
seekBackwardButton.setBackgroundImage(UIImage(systemName: "gobackward.15"), for: .normal)
playPauseButton.tintColor = UIColor(named: "PlayerColors")
nextButton.tintColor = UIColor(named: "PlayerColors")
backButton.tintColor = UIColor(named: "PlayerColors")
seekForwardButton.tintColor = UIColor(named: "PlayerColors")
seekBackwardButton.tintColor = UIColor(named: "PlayerColors")
holder.addSubview(playPauseButton)
holder.addSubview(nextButton)
holder.addSubview(backButton)
holder.addSubview(seekForwardButton)
holder.addSubview(seekBackwardButton)
}
#objc func panGesture(gesture: UIPanGestureRecognizer) {
let currentPoint = gesture.location(in: playbackSlider)
let percentage = currentPoint.x/playbackSlider.bounds.size.width;
let delta = Float(percentage) * (playbackSlider.maximumValue - playbackSlider.minimumValue)
let value = playbackSlider.minimumValue + delta
playbackSlider.setValue(value, animated: true)
}
#objc func progressScrubbed(_ playbackSlider: UISlider!) {
let seconds : Int64 = Int64(playbackSlider.value)
let targetTime:CMTime = CMTimeMake(value: seconds, timescale: 1)
player!.seek(to: targetTime)
if player!.rate == 0
{
player?.play()
}
}
func setupNowPlaying() {
// Define Now Playing Info
var nowPlayingInfo = [String : Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = "Unstoppable"
if let image = UIImage(named: "artist") {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { size in
return image
}
}
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentTime
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = playerItem?.duration
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate
// Set the metadata
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
func updateNowPlaying(isPause: Bool) {
// Define Now Playing Info
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo!
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentTime
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPause ? 0 : 1
// Set the metadata
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
func setupNotifications() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: nil)
notificationCenter.addObserver(self,
selector: #selector(handleRouteChange),
name: AVAudioSession.routeChangeNotification,
object: nil)
}
#objc func handleRouteChange(notification: Notification) {
guard let userInfo = notification.userInfo,
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSession.RouteChangeReason(rawValue:reasonValue) else {
return
}
switch reason {
case .newDeviceAvailable:
let session = AVAudioSession.sharedInstance()
for output in session.currentRoute.outputs where output.portType == AVAudioSession.Port.headphones {
print("headphones connected")
DispatchQueue.main.sync {
player?.play()
}
break
}
case .oldDeviceUnavailable:
if let previousRoute =
userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
for output in previousRoute.outputs where output.portType == AVAudioSession.Port.headphones {
print("headphones disconnected")
DispatchQueue.main.sync {
player?.pause()
}
break
}
}
default: ()
}
}
#objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
print("Interruption began")
// Interruption began, take appropriate actions
}
else if type == .ended {
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// Interruption Ended - playback should resume
print("Interruption Ended - playback should resume")
player?.play()
} else {
// Interruption Ended - playback should NOT resume
print("Interruption Ended - playback should NOT resume")
}
}
}
}
#objc func didTapPlayPauseButton() {
if player?.timeControlStatus == .playing {
//pause
player?.pause()
//show play button
playPauseButton.setBackgroundImage(UIImage(systemName: "play.fill"), for: .normal)
//shrink image
UIView.animate(withDuration: 0.2, animations: {
self.albumImageView.frame = CGRect(x: 50,
y: 50,
width: self.holder.frame.size.width - 100,
height: self.holder.frame.size.width - 100)
})
} else {
//play
player?.play()
//show pause button
playPauseButton.setBackgroundImage(UIImage(systemName: "pause.fill"), for: .normal)
//increase image size
UIView.animate(withDuration: 0.4, animations: {
self.albumImageView.frame = CGRect(x: 20,
y: 20,
width: self.holder.frame.size.width - 40,
height: self.holder.frame.size.width - 40)
})
}
}
This is Audio Struct
import Foundation
import UIKit
struct Audio {
let image: UIImage?
let name: String
let albumName: String
let paragraphNumber: String
let audioImageName: String
let trackURL: String
}
This is customTableViewCell
import UIKit
class AudioCustomTableViewCell: UITableViewCell {
#IBOutlet weak var cellView: UIView!
#IBOutlet weak var audioImage: UIImageView!
#IBOutlet weak var mainTitle: UILabel!
#IBOutlet weak var detailTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setCardView(toView: audioImage)
setCardView(toView: cellView)
self.audioImage.layer.cornerRadius = 6
audioImage.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func commonInit(_ title: String, _ subtitle: String, _ image: UIImage?){
mainTitle.text = title
detailTitle.text = subtitle
audioImage.image = image
}
func setCardView(toView: UIView) {
toView.layer.shadowColor = UIColor.black.cgColor
toView.layer.shadowOpacity = 0.3
toView.layer.shadowRadius = 3
toView.layer.shadowOffset = CGSize(width: 6, height: 6)
toView.layer.shadowRadius = 8
toView.layer.cornerRadius = 6
}
}
when you are navigating from didSelectRowAt Indexpath please pass all the values to your detailViewController when you navigate to that view all value will be there to use.
use let song = viewModel.audios[indexPath.row]
let imageURL = song.audioImageName
Get values and pass it to your detailViewController
do the same thing in didSelectRowAt Indexpath what you did in cellForRowAt indexPath because there you are just passing position and taking values from model and passing it that's why this might be happening.
if you are getting only this error cannot assign UIImage to [UIImage]
then you can just convert your UIImage to [UIImage] and assign it to your [UIImage]
This line causes the bug, viewModel.image will always be the last download one.
self.viewModel.image = cell?.audioImage.image
you can simply pass the image URL to the next VC
vc.mainImage = viewModel.audios[indexPath.row].audioImageName
then use SDWebImage to load this image from the url
I tried the other method of SDWebImage and in didselectRowAt according to suggestion of Rahul Nimje. Thanks to him it worked. Dear Rahul, how will it affect on weight of the app, is it the correct way?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let position = indexPath.row
let song = viewModel.audios[indexPath.row]
let imageURL = song.audioImageName
SDWebImageManager.shared.loadImage(
with: URL(string: imageURL),
options: .continueInBackground, // or .highPriority
progress: nil,
completed: { [weak self] (image, data, error, cacheType, finished, url) in
guard let self = self else { return }
if let err = error {
// Do something with the error
return
}
guard let img = image else {
// No image handle this error
return
}
// Do something with image
self.viewModel.image = image
})

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
}

How to create a custom segment control in Swift

I want to create a segment control that works like in the screenshot.
The selected segment should be underlined according to the segment heading text. I have searched for that, but did not find any third party solution.
So how can I develop this type of segment control?
Here you can see that the line at the bottom only stretches across the selected segment.
There is an open source project in GitHub named PageMenu. Please have a look, you can even customize the source file CAPSPageMenu.
https://github.com/PageMenu/PageMenu
To update width of the selection hair line, enable the below property.
menuItemWidthBasedOnTitleTextWidth
Code:
let parameters: [CAPSPageMenuOption] = [
...
.menuItemWidthBasedOnTitleTextWidth(true),
....]
// Initialize scroll menu
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height), pageMenuOptions: parameters)
Please try PageMenuDemoStoryboard demo in the project and update parameters as shown in above code.
import UIKit
extension UIView {
func constraintsEqualToSuperView() {
if let superview = self.superview {
NSLayoutConstraint.activate(
[
self.topAnchor.constraint(
equalTo: superview.topAnchor
),
self.bottomAnchor.constraint(
equalTo: superview.bottomAnchor
),
self.leadingAnchor.constraint(
equalTo: superview.leadingAnchor
),
self.trailingAnchor.constraint(
equalTo: superview.trailingAnchor
)
]
)
}
}
}
protocol ButtonsViewDelegate: class {
func didButtonTap(buttonView: ButtonsView, index: Int)
}
class ButtonsView: UIView {
fileprivate let stackView = UIStackView()
fileprivate var array = [String]()
fileprivate var buttonArray = [UIButton]()
fileprivate let baseTag = 300
weak var delegate: ButtonsViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setupUI () {
setupStackView()
setupButton()
}
convenience init(buttons: [String]) {
self.init()
array = buttons
setupUI()
//selectButton(atIndex: 0)
}
fileprivate func setupStackView() {
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .fillEqually
stackView.constraintsEqualToSuperView()
}
fileprivate func setupButton() {
for (i,string) in array.enumerated() {
let button = UIButton()
button.setTitle(string.uppercased(), for: .normal)
// button.backgroundColor = UIColor.lightBackgroundColor().lightened(by: 0.2)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(
self,
action: #selector(buttonClicked(sender:)),
for: .touchUpInside
)
button.setTitleColor(
.black,
for: .normal
)
button.titleLabel?.font = UIFont.systemFont(
ofSize: 14,
weight: UIFont.Weight.bold
)
// let view = UIView.init(frame: CGRect.init(x: 0, y: button.frame.size.height - 1, width: button.frame.size.width, height: 1))
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.clear
view.tag = baseTag + i
button.addSubview(view)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(
equalTo: button.leadingAnchor
),
view.trailingAnchor.constraint(
equalTo: button.trailingAnchor
),
view.bottomAnchor.constraint(
equalTo: button.bottomAnchor
),
view.heightAnchor.constraint(
equalToConstant: 1
)
])
stackView.addArrangedSubview(button)
buttonArray.append(button)
}
}
func selectButton(atIndex index: Int) {
if index <= buttonArray.count {
buttonClicked(sender: buttonArray[index])
}
}
#objc private func buttonClicked(sender: UIButton) {
for button in buttonArray {
if button == sender {
button.setTitleColor(
UIColor.darkGray,
for: .normal
)
setUpBottomLine(button: button)
}else{
button.setTitleColor(
.black,
for: .normal
)
hideBottomLine(button: button)
}
}
if let index = buttonArray.index(of: sender) {
delegate?.didButtonTap(buttonView: self, index: index)
}
}
private func setUpBottomLine(button: UIButton) {
if let index = buttonArray.index(of: button) {
if let view = button.viewWithTag(baseTag + index) {
view.backgroundColor = UIColor.red
}
}
}
private func hideBottomLine(button: UIButton) {
if let index = buttonArray.index(of: button) {
if let view = button.viewWithTag(baseTag + index) {
view.backgroundColor = .clear
}
}
}
}
//how to use
let durationBtns = ButtonsView(buttons: [
NSLocalizedString(
"day",
comment: ""
),
NSLocalizedString(
"week",
comment: ""
),
NSLocalizedString(
"month",
comment: ""
),
NSLocalizedString(
"year",
comment: ""
)
])
durationButtons = durationBtns
durationButtons.selectButton(atIndex: 0)
durationBtns.delegate = self
//handle buttton tap
extension viewController: ButtonsViewDelegate {
func didButtonTap(buttonView: ButtonsView, index: Int) {
print(index.description)
}
}

Scroll to bottom causes crash

So I have this snippet of code that is supposed to handle making my content scroll to the bottom upon toggling of the keyboard. However, all it does is crash every time that I try to do it.
Any insight?
Here is my code:
#objc func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo{
let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = IndexPath(item: self.comments.count-1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
})
}
}
I am currently using IGLitkit to populate my view controller. The items are controlled and loaded through this view controller.
import UIKit
import IGListKit
import Foundation
protocol CommentsSectionDelegate: class {
func CommentSectionUpdared(sectionController: CommentsSectionController)
}
class CommentsSectionController: ListSectionController,CommentCellDelegate {
weak var delegate: CommentsSectionDelegate? = nil
var comment: CommentGrabbed?
var eventKey: String?
override init() {
super.init()
// supplementaryViewSource = self
//sets the spacing between items in a specfic section controller
inset = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
return 1
}
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
let dummyCell = CommentCell(frame: frame)
dummyCell.comment = comment
dummyCell.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
let height = max(40+8+8, estimatedSize.height)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override var minimumLineSpacing: CGFloat {
get {
return 0.0
}
set {
self.minimumLineSpacing = 0.0
}
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
fatalError()
}
// print(comment)
cell.comment = comment
cell.delegate = self
return cell
}
override func didUpdate(to object: Any) {
comment = object as? CommentGrabbed
}
override func didSelectItem(at index: Int){
}
/*
func supportedElementKinds() -> [String] {
return [UICollectionElementKindSectionHeader]
}
func viewForSupplementaryElement(ofKind elementKind: String, at index: Int) -> UICollectionReusableView {
guard let view = collectionContext?.dequeueReusableSupplementaryView(ofKind: elementKind, for: self, class: CommentHeader.self, at: index) as? CommentHeader else{
fatalError()
}
view.handle = "Comments"
return view
}
*/
// func optionsButtonTapped(cell: CommentCell) {
// print("like")
//
//
// }
func optionsButtonTapped(cell: CommentCell){
print("like")
let comment = self.comment
_ = comment?.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment?.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment!)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment!, (comment?.eventKey)!)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
self.onItemDeleted()
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
self.viewController?.present(alertController, animated: true, completion: nil)
}
func onItemDeleted() {
delegate?.CommentSectionUpdared(sectionController: self)
}
/*
func sizeForSupplementaryView(ofKind elementKind: String, at index: Int) -> CGSize {
return CGSize(width: collectionContext!.containerSize.width, height: 40)
}
*/
}
Also note the data source
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var items:[ListDiffable] = comments
print("comments = \(comments)")
return [addHeader] + items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
if let object = object as? ListDiffable, object === addHeader {
return CommentsHeaderSectionController()
}
let sectionController = CommentsSectionController()
sectionController.delegate = self
return sectionController
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
Controller which contains keyboard function that causes crash
import UIKit
import IGListKit
import Firebase
class NewCommentsViewController: UIViewController, UITextFieldDelegate,CommentsSectionDelegate {
//array of comments which will be loaded by a service function
var comments = [CommentGrabbed]()
var messagesRef: DatabaseReference?
var bottomConstraint: NSLayoutConstraint?
public let addHeader = "addHeader" as ListDiffable
public var eventKey = ""
//This creates a lazily-initialized variable for the IGListAdapter. The initializer requires three parameters:
//1 updater is an object conforming to IGListUpdatingDelegate, which handles row and section updates. IGListAdapterUpdater is a default implementation that is suitable for your usage.
//2 viewController is a UIViewController that houses the adapter. This view controller is later used for navigating to other view controllers.
//3 workingRangeSize is the size of the working range, which allows you to prepare content for sections just outside of the visible frame.
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
}()
// 1 IGListKit uses IGListCollectionView, which is a subclass of UICollectionView, which patches some functionality and prevents others.
let collectionView: UICollectionView = {
// 2 This starts with a zero-sized rect since the view isn’t created yet. It uses the UICollectionViewFlowLayout just as the ClassicFeedViewController did.
let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
// 3 The background color is set to white
view.backgroundColor = UIColor.white
return view
}()
//will fetch the comments from the database and append them to an array
fileprivate func fetchComments(){
comments.removeAll()
messagesRef = Database.database().reference().child("Comments").child(eventKey)
print(eventKey)
// print(comments.count)
var query = messagesRef?.queryOrderedByKey()
query?.observe(.value, with: { (snapshot) in
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else {
return
}
print(snapshot)
allObjects.forEach({ (snapshot) in
guard let commentDictionary = snapshot.value as? [String: Any] else{
return
}
guard let uid = commentDictionary["uid"] as? String else{
return
}
UserService.show(forUID: uid, completion: { (user) in
if let user = user {
var commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
commentFetched.commentID = snapshot.key
let filteredArr = self.comments.filter { (comment) -> Bool in
return comment.commentID == commentFetched.commentID
}
if filteredArr.count == 0 {
self.comments.append(commentFetched)
}
self.adapter.performUpdates(animated: true)
}
self.comments.sort(by: { (comment1, comment2) -> Bool in
return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
})
self.comments.forEach({ (comments) in
})
})
})
}, withCancel: { (error) in
print("Failed to observe comments")
})
//first lets fetch comments for current event
}
lazy var submitButton : UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
submitButton.isEnabled = false
return submitButton
}()
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
containerView.addSubview(self.submitButton)
self.submitButton.anchor(top: containerView.topAnchor, left: nil, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
containerView.addSubview(self.commentTextField)
self.commentTextField.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: self.submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
self.commentTextField.delegate = self
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
containerView.addSubview(lineSeparatorView)
lineSeparatorView.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: nil, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
return containerView
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
#objc func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.characters.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
#objc func handleSubmit(){
guard let comment = commentTextField.text, comment.characters.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will remove text after entered
self.commentTextField.text = nil
}
#objc func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo {
if let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = IndexPath(item: self.comments.count-1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
})
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.addSubview(containerView)
collectionView.alwaysBounceVertical = true
containerView.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 40)
//view.addConstraintsWithFormatt("H:|[v0]|", views: containerView)
// view.addConstraintsWithFormatt("V:[v0(48)]", views: containerView)
bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint!)
adapter.collectionView = collectionView
adapter.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "CommentCell")
collectionView.register(CommentHeader.self, forCellWithReuseIdentifier: "HeaderCell")
self.collectionView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0)
fetchComments()
// Do any additional setup after loading the view.
}
//look here
func CommentSectionUpdared(sectionController: CommentsSectionController){
print("like")
self.fetchComments()
self.adapter.performUpdates(animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
submitButton.isUserInteractionEnabled = true
}
//viewDidLayoutSubviews() is overridden, setting the collectionView frame to match the view bounds.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var items:[ListDiffable] = comments
print("comments = \(comments)")
return [addHeader] + items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
if let object = object as? ListDiffable, object === addHeader {
return CommentsHeaderSectionController()
}
let sectionController = CommentsSectionController()
sectionController.delegate = self
return sectionController
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}

Resources