delegate not being called in Network class - swift

I am working with stackoverflow api in Worker class I am getting data and adding to tableView. I managed to connect and get the data and I'm trying to add it to the main view controller using a delegate, but the compiler ignores this line, what could be the matter?
When writing to the searchButton procedure, I call a method of the Worker class that receives data from the API and adds it to the array. Then, with the help of a delegate, I return a mass, but this delegate is not called.
class viewController
import UIKit
protocol WorkerDelegate: AnyObject {
func getListModels(noteModels: [CellModel])
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, WorkerDelegate{
private var listModels: [CellModel] = []
private var searchText = UITextField()
private var searchButton = UIButton()
private var table = UITableView()
private let worker = Worker()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let worker = Worker()
worker.workerDelegate = self
setupSearchText()
setupSearchButton()
table.register(CustomCell.self, forCellReuseIdentifier: "Cell")
table.delegate = self
table.dataSource = self
setupUI()
// Do any additional setup after loading the view.
}
func getListModels(noteModels: [CellModel]) {
listModels += noteModels
table.reloadData()
}
private func setupSearchText() {
searchText.translatesAutoresizingMaskIntoConstraints = false
searchText.font = UIFont.systemFont(ofSize: 14, weight: .bold)
searchText.textColor = .black
searchText.backgroundColor = .white
searchText.borderStyle = UITextField.BorderStyle.roundedRect
searchText.layer.borderWidth = 1
searchText.layer.borderColor = UIColor.black.cgColor
//date.textAlignment = .center
view.addSubview(searchText)
searchText.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
searchText.leadingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20
).isActive = true
searchText.trailingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -70
).isActive = true
}
private func setupSearchButton() {
searchButton.translatesAutoresizingMaskIntoConstraints = false
// searchButton.font = UIFont.systemFont(ofSize: 14, weight: .bold)
// searchButton.textColor = .black
searchButton.backgroundColor = .white
// searchButton.borderStyle = UITextField.BorderStyle.roundedRect
searchButton.layer.borderWidth = 1
searchButton.setTitle("OK",
for: .normal)
// searchButton.titleLabel?.font = UIFont.systemFont(ofSize: 24)
searchButton.setTitleColor(.black, for: .normal)
searchButton.layer.borderColor = UIColor.black.cgColor
//date.textAlignment = .center
view.addSubview(searchButton)
searchButton.addTarget(self, action: #selector(didSearchButtonnTap(_:)), for: .touchUpInside)
searchButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
searchButton.leadingAnchor.constraint(
equalTo: searchText.safeAreaLayoutGuide.trailingAnchor, constant: 10
).isActive = true
searchButton.trailingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20
).isActive = true
}
private func setupUI() {
table.translatesAutoresizingMaskIntoConstraints = false
table.separatorStyle = .none
view.addSubview(table)
table.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
table.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
table.topAnchor.constraint(equalTo: searchText.bottomAnchor,constant: 20).isActive = true
table.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
listModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? CustomCell else {
fatalError("не CustomCell")
}
cell.selectionStyle = .none
cell.model = listModels[indexPath.row]
cell.layer.cornerRadius = 14
cell.layer.shadowRadius = 14
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// нажатие
}
#objc private func didSearchButtonnTap(_ sender: Any) {
worker.fetch(question: searchText.text ?? "")
table.reloadData()
}
}
class Worker
import Foundation
class Worker {
let session: URLSession
var workerDelegate: WorkerDelegate?
func fetch(question: String) {
var listModels: [CellModel] = [CellModel(title: "123",name: "312",date: Date.now, answer_count: Int32(0)
)]
if let url = createURLComponents(question: question) {
url.asyncDownload { data, _, error in
guard let data = data else {
print("URLSession dataTask error:", error ?? "nil")
return
}
do {
let jsonObject = try JSONDecoder().decode(SearchModels.self, from: data)
_ = String(data: data, encoding: .utf8)
for object in jsonObject.items {
let timeInterval = TimeInterval(object.creationDate)
let newDate = Date(timeIntervalSince1970: timeInterval)
var model = CellModel(
title: object.title,
name: object.owner.displayName,
date: newDate,
answer_count: Int32(object.answerCount)
)
listModels.append(model)
}
DispatchQueue.main.async {
self.workerDelegate?.getListModels(noteModels: listModels)
}
} catch {
print(error.localizedDescription)
}
}
}
}
init(
session: URLSession = URLSession(configuration: .default)
) {
self.session = session
}
private func createURLComponents(question: String) -> URL? {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.stackexchange.com"
urlComponents.path = "/2.3/search"
urlComponents.queryItems = [
URLQueryItem(name: "order", value: "desc"),
URLQueryItem(name: "sort", value: "activity"),
URLQueryItem(name: "intitle", value: question),
URLQueryItem(name: "site", value: "stackoverflow")
]
return urlComponents.url!
}
private func createURLRequest(question: String) -> URLRequest {
var request = URLRequest(url: createURLComponents(question: question)!)
request.httpMethod = "GET"
return request
}
}
extension URL {
func asyncDownload(completion: #escaping (_ data: Data?, _ response: URLResponse?, _ error: Error?) -> Void) {
URLSession.shared
.dataTask(with: self, completionHandler: completion)
.resume()
}
}

Related

How to dynamically update my gallery data

Hello Respected Comrades,
I made a gallery app. initially I get a data of media from phone.
but when i add some new photos to device. i cant see them until :
re install app
fetching in view will appear (this is just a temperory fix)
I want to update my data dynamically with respect to phone data
code:
import UIKit
import Foundation
import Photos
import CoreData
enum MediaType{
case video, photo
}
View model
class GalleryPageViewModel : BaseViewModel{
var mediaArray = Spectator<MediaArray?>(value:
[])
var title = "Gallery"
var appDelegate:AppDelegate?
var context : NSManagedObjectContext?
let responder : AppResponder
var tempMedia = [MediaModel]()
init(with responder:AppResponder){
self.responder = responder
super.init()
}
}
extension GalleryPageViewModel{
func populatePhotos(){
mediaArray.value = []
tempMedia = []
PHPhotoLibrary.requestAuthorization{ [weak self] status in
if status == .authorized{
let assets = PHAsset.fetchAssets(with: nil)
assets.enumerateObjects{ (object,_,_) in
var image = UIImage()
image = self!.conversionPhotoToImage(asset: object)
var mediaType : MediaType = .photo
if object.mediaType == .video{
mediaType = .video
}
let media = MediaModel(img: image, asset: object, mediaType: mediaType)
self?.tempMedia.append(media)
}
}
else{
print("auth failed")
}
self?.setUpData()
}
}
func conversionPhotoToImage(asset : PHAsset) -> UIImage{
let manager = PHImageManager.default()
var media = UIImage()
let requestOptions=PHImageRequestOptions()
requestOptions.isSynchronous=true
requestOptions.deliveryMode = .highQualityFormat
manager.requestImage(for: asset, targetSize: CGSize(width: 1000, height: 1000), contentMode:.aspectFill, options: requestOptions) { image,_ in
media = image!
}
return media
}
func setUpData(){
self.mediaArray.value = tempMedia
}
}
extension GalleryPageViewModel{
func numberOfItemsInSection() -> Int{
return mediaArray.value?.count ?? 0
}
func didSelectAtRow(index:IndexPath){
self.responder.pushToDetailVc(data: mediaArray.value![index.row])
}
}
extension GalleryPageViewModel{
func setUpCoreData(){
appDelegate = UIApplication.shared.delegate as! AppDelegate
context = appDelegate?.persistentContainer.viewContext
}
func storeInCoreData(){
let entity = NSEntityDescription.entity(forEntityName: "MediaData", in: context!)
let newUser = NSManagedObject(entity: entity!, insertInto: context)
var i = 1
for data in mediaArray.value!{
newUser.setValue(data.img, forKey: "image")
newUser.setValue(data.duration, forKey: "duration")
newUser.setValue("data", forKey: "mediaType")
newUser.setValue(data.location, forKey: "location")
newUser.setValue(data.creationDate, forKey: "creationDate")
var id = String()
if data.mediaType == .photo{
id = "imgEn\(i)"
}
else{
id = "vidEn\(i)"
}
i+=1
newUser.setValue(id, forKey: "id")
}
}
}
View controller
import UIKit
import Photos
class GalleryPageViewController: BaseViewController {
var galleryCollectionView : UICollectionView?
var galleryCollectionViewFlowLayout : UICollectionViewFlowLayout? = nil
var viewModel: GalleryPageViewModel? {
didSet {
self.bindViewModel()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = viewModel?.title
view.backgroundColor = .systemBackground
setUpFlowLayout()
galleryCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: galleryCollectionViewFlowLayout!)
galleryCollectionView?.register(MediaCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
setUpCollectionView()
viewConstraintsForGalleryPage()
viewModel?.populatePhotos()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
// MARK: - UI WORKS
extension GalleryPageViewController{
func setUpFlowLayout(){
galleryCollectionViewFlowLayout = UICollectionViewFlowLayout()
galleryCollectionViewFlowLayout?.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
galleryCollectionViewFlowLayout?.itemSize = CGSize(width: (view.frame.width/2) - 20, height: (self.view.frame.width/2)-10)
}
func setUpCollectionView(){
galleryCollectionView?.dataSource = self
galleryCollectionView?.delegate = self
}
func viewConstraintsForGalleryPage(){
// adding sub view to maintain heirarchy.
view.addSubview(galleryCollectionView!)
// constraints for views in this page
// collection view
galleryCollectionView?.translatesAutoresizingMaskIntoConstraints = false
// galleryCollectionView?.backgroundColor = .blue
galleryCollectionView?.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,constant: 10).isActive = true
galleryCollectionView?.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
galleryCollectionView?.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,constant: 10).isActive = true
galleryCollectionView?.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
}
}
//MARK: - COLLECTION VIEW DELEGATE AND DATA SOURCE
extension GalleryPageViewController : UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// vc.data = viewModel?.mediaArray.value?[indexPath.row]
viewModel?.didSelectAtRow(index: indexPath)
}
}
extension GalleryPageViewController : UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel?.numberOfItemsInSection() ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MediaCollectionViewCell
let cellData = viewModel?.mediaArray.value?[indexPath.row]
cell.imageView.image = cellData?.img
if cellData?.mediaType == .video{
cell.playBtn.isHidden = false
cell.durationLabel.isHidden = false
let manager = PHImageManager.default()
manager.requestAVAsset(forVideo: (cellData?.asset)!, options: nil){asset,_,_ in
let duration = asset?.duration
let durationTime = CMTimeGetSeconds(duration!)
let finalTime = durationTime/60
DispatchQueue.main.async {
cell.durationLabel.text = "\(Int(finalTime)) : \(Int(durationTime) % 60)"
}
}
}
else{
cell.playBtn.isHidden = true
cell.durationLabel.isHidden = true
}
return cell
}
}
//MARK: - BINDING VARIABLES
extension GalleryPageViewController{
private func bindViewModel(){
viewModel?.mediaArray.bind { [weak self] _ in
print("i am gonna reload")
print(self?.viewModel?.mediaArray.value?.isEmpty)
DispatchQueue.main.async {
self?.galleryCollectionView?.reloadData()
}
}
}
}

Design the custom table view cell programatically

I created table view cell programmatically and set the constrains as well.It is able to display the image , firstname , lastname property into cell but when I added new label with values , it not displaying the values .
Here is the cell code .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let jobTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
containerView.addSubview(jobTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.lastnameTitleLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.lastnameTitleLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String,jobtitle: String ) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
jobTitleLabel.text = "Occupation : \(jobtitle)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
private var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(activityIndicator)
view.addSubview(tableView)
// self.tableView.rowHeight = 100.00
tableView.rowHeight = UITableView.automaticDimension
tableView.rowHeight = 100
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName,jobtitle: people.jobtitle)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the screenshot . As it not showing the job title property into cell .
I think your problem is the height of your containerView
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
I think 40 is to low to show the jobTitleLabel

Error Thread 1: EXC_BAD_ACCESS (code=1, address=0x30)

Please tell me the application crashes with an error:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x30)
when clicking on the button for reminders for a table cell (switching to a new View Controller).
The error appears in the class when creating a UILabel in the line let label = UILabel ():
let oneLabel: UILabel = {
let label = UILabel()
if (UIDevice.current.userInterfaceIdiom == .pad) {
label.font = UIFont.systemFont(ofSize: 32, weight: .semibold)
label.numberOfLines = 4
}
else {
label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
label.numberOfLines = 1
}
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Here is the code from another class that transitions to the View Controller, for which the UILabel is created:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let alarm = UIContextualAction(
style: .normal,
title: "",
handler: {(_, _, completion) in
self.notificationCenter.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
guard granted else { return DispatchQueue.main.async { self.createAlertForNotifications() } }
self.notificationCenter.getNotificationSettings { (settings) in
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
let vc = OneViewController()
self.parentController!.navigationController?.setViewControllers([vc], animated: false)
}
completion(true)
}
}
}
)
alarm.image = ListImages.alarmImage
alarm.backgroundColor = .systemPurple
return UISwipeActionsConfiguration(actions: [alarm])
}
Sometimes this error appears, sometimes not, and it may appear for the second UILabel, which is created after the first. How can you fix this error?
Sometimes I get error in let dataPicker = UIDatePicker()
Here you go to the OneViewController:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let oneObject = isFiltering ? filteredObjects[indexPath.row] : listObjects[indexPath.row]
let alarm = UIContextualAction(
style: .normal,
title: "",
handler: {(_, _, completion) in
self.notificationCenter.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
guard granted else { return DispatchQueue.main.async { self.createAlertForNotifications() } }
self.notificationCenter.getNotificationSettings { (settings) in
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
let vc = OneViewController()
ListNameLabel.oneText = oneObject.name
self.parentController!.navigationController?.setViewControllers([vc], animated: false)
}
completion(true)
}
}
}
)
alarm.image = ListImages.alarmImage
alarm.backgroundColor = .systemPurple
return UISwipeActionsConfiguration(actions: [alarm])
}
This is class with UILabel:
import UIKit
class MainView: UIView {
let oneLabel: UILabel = {
let label = UILabel()
if (UIDevice.current.userInterfaceIdiom == .pad) {
label.font = UIFont.systemFont(ofSize: 32, weight: .semibold)
label.numberOfLines = 4
}
else {
label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
label.numberOfLines = 1
}
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let sheduleTimeDataPicker: UIDatePicker = {
let dataPicker = UIDatePicker()
if #available(iOS 14, *) {
dataPicker.preferredDatePickerStyle = .inline
}
else {
dataPicker.preferredDatePickerStyle = .compact
}
dataPicker.datePickerMode = .dateAndTime
dataPicker.date = Date()
dataPicker.minimumDate = Date()
dataPicker.translatesAutoresizingMaskIntoConstraints = false
return dataPicker
}()
let oneView: UIView = {
let view = UIView()
view.backgroundColor = .systemGray5
view.layer.cornerRadius = 15
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(oneView)
oneView.addSubview(oneLabel)
oneView.addSubview(sheduleTimeDataPicker)
if (UIDevice.current.userInterfaceIdiom == .pad) {
// oneLabel constraints
oneLabel.leadingAnchor.constraint(equalTo: oneView.leadingAnchor, constant: 20).isActive = true
oneLabel.trailingAnchor.constraint(equalTo: oneView.trailingAnchor, constant: -20).isActive = true
oneLabel.topAnchor.constraint(equalTo: oneView.topAnchor, constant: 20).isActive = true
oneLabel.heightAnchor.constraint(equalToConstant: 120).isActive = true
// sheduleTimeDataPicker constraints
sheduleTimeDataPicker.centerXAnchor.constraint(equalTo: oneView.centerXAnchor).isActive = true
sheduleTimeDataPicker.topAnchor.constraint(equalTo: oneLabel.bottomAnchor).isActive = true
}
else {
// oneLabel constraints
oneLabel.leadingAnchor.constraint(equalTo: oneView.leadingAnchor, constant: 10).isActive = true
oneLabel.trailingAnchor.constraint(equalTo: oneView.trailingAnchor, constant: -10).isActive = true
oneLabel.topAnchor.constraint(equalTo: oneView.topAnchor, constant: 5).isActive = true
oneLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true
// sheduleTimeDataPicker constraints
sheduleTimeDataPicker.leadingAnchor.constraint(equalTo: oneView.leadingAnchor, constant: 10).isActive = true
sheduleTimeDataPicker.trailingAnchor.constraint(equalTo: oneView.trailingAnchor, constant: -10).isActive = true
sheduleTimeDataPicker.topAnchor.constraint(equalTo: oneLabel.bottomAnchor).isActive = true
}
// oneView constraints
oneView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
oneView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10).isActive = true
oneView.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
oneView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10).isActive = true
}
func setContentView(content: OneModel) {
self.oneLabel.text = content.oneName
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is class OneModel:
import UIKit
struct ListNameLabel {
static var oneText = ""
}
struct OneModel {
var oneName: String
static func fetchView() -> OneModel {
return OneModel(oneName: ListNameLabel.oneText)
}
}
This is class OneViewController:
import UIKit
import UserNotifications
class OneViewController: UIViewController {
var mainView = MainView()
let notificationCenter = UNUserNotificationCenter.current()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupNavigationBar()
setupView()
}
// MARK: NavigationBar
private func setupNavigationBar() {
let backBarButtonItem = UIBarButtonItem()
backBarButtonItem.image = ListImages.chevronImage
backBarButtonItem.action = #selector(backBarButtonItemTapped)
backBarButtonItem.target = self
navigationItem.leftBarButtonItem = backBarButtonItem
navigationItem.title = ""
}
// MARK: View
private func setupView() {
view.backgroundColor = .systemBackground
view.addSubview(mainView)
mainView.translatesAutoresizingMaskIntoConstraints = false
let guide = self.view.safeAreaLayoutGuide
// mainView constraints
mainView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
mainView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
mainView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
mainView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
// fetch
mainView.setContentView(content: OneModel.fetchView())
}
}
// MARK: Back
extension OneViewController {
#objc func backBarButtonItemTapped() {
let vc = TwoViewController()
self.navigationController?.setViewControllers([vc], animated: false)
}
}
// MARK: UNUserNotificationCenterDelegate
extension OneViewController: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.banner, .sound])
print(#function)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print(#function)
}
}

The background of the TableView at the bottom displays a blank cell

I have this UITableView that has xib files as cells and it all works as intended, however the last cell only when I scroll down doesn't have a background color. It only happens when I scroll down and then it pops back to normal position. I'm essentially wondering how I can change the color of that cell just to my background color. Thank you.
//
// BlogViewController.swift
// testAPI
//
// Created by Dilan Piscatello on 4/2/20.
// Copyright © 2020 Dilan Piscatello. All rights reserved.
//
import Foundation
import UIKit
import Firebase
class BlogViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
var x = 0
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return 1
}else{
return posts.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
let cell = Bundle.main.loadNibNamed("QuestionTableViewCell", owner: self, options: nil)?.first as! QuestionTableViewCell
print("this is how many times it ran")
cell.setPost(question: self.question)
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "postcell", for: indexPath) as! PostTableViewCell
cell.setPost(post: posts[indexPath.row])
return cell
}
}
var question = "sdfsdfs"
var tableView:UITableView!
var lastUploadPostID:String?
var discussion:UILabel! = UILabel()
var posts = [Post]()
var fetchingMore = false
var endReached = false
let leadingScreensForBatching:CGFloat = 3.0
var cellHeights: [IndexPath:CGFloat] = [:]
var refreshControl:UIRefreshControl!
var postRef:DatabaseReference{
return Database.database().reference().child("posts")
}
var oldPostQuery:DatabaseQuery{
var queryRef:DatabaseQuery
let lastPost = self.posts.last
if lastPost == nil{
queryRef = postRef.queryOrdered(byChild: "timestamp")
}else{
let lastTimestamp = lastPost!.createdAt.timeIntervalSince1970*1000
queryRef = postRef.queryOrdered(byChild: "timestamp").queryEnding(atValue: lastTimestamp)
}
return queryRef
}
var newPostQuery:DatabaseQuery{
var queryRef:DatabaseQuery
let firstPost = self.posts.first
if firstPost == nil{
queryRef = postRef.queryOrdered(byChild: "timestamp")
}else{
let firstTimestamp = firstPost!.createdAt.timeIntervalSince1970*1000
queryRef = postRef.queryOrdered(byChild: "timestamp").queryStarting(atValue: firstTimestamp)
}
return queryRef
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func hi(){
let db = Firestore.firestore()
db.collection("blog").document("question").getDocument { (document,error) in
if error != nil{
print("cant get data")
}
if document != nil && document!.exists{
if let documentdata = document?.data() {
self.question = documentdata["question"] as! String
self.tableView.reloadData()
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
discussion.text = "Discussion"
//longTitleLabel.font = ................
discussion.font = UIFont(name: "HelveticaNeue-Bold", size: 31)
discussion.translatesAutoresizingMaskIntoConstraints = false
if let navigationBar = self.navigationController?.navigationBar {
navigationBar.addSubview(discussion)
//navigationBar.shadowImage = UIImage()
// navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
// navigationBar.isTranslucent = true
navigationBar.barTintColor = UIColor( red: 128/255, green: 117/255, blue: 255/255, alpha: 1)
discussion.leftAnchor.constraint(equalTo: navigationBar.leftAnchor, constant: 22).isActive = true
discussion.widthAnchor.constraint(equalToConstant: 300).isActive = true
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
self.tableView.separatorStyle = .none
view.addSubview(tableView)
hi()
print(question)
// Do any additional setup after loading the view.
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postcell")
var layoutGuide:UILayoutGuide!
layoutGuide = view.safeAreaLayoutGuide
tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
tableView.reloadData()
refreshControl = UIRefreshControl()
tableView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
beginBatchFetch()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// if let navigationController = navigationController as? ScrollingNavigationController {
// navigationController.followScrollView(tableView, delay: 0.0)
//}
listenForNewPosts()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopListeningForNewPosts()
}
#objc func handleRefresh(){
print()
newPostQuery.queryLimited(toFirst: 20).observeSingleEvent(of: .value, with: { (snapshot) in
var tempPosts = [Post]()
let firstPost = self.posts.first
for child in snapshot.children{
if let childSnapshot = child as? DataSnapshot,
let dict = childSnapshot.value as? [String:Any],
let post = Post.parse(childSnapshot.key, dict),
childSnapshot.key != firstPost?.id{
tempPosts.insert(post, at: 0)
}
}
self.posts.insert(contentsOf: tempPosts, at: 0)
self.tableView.reloadData()
self.refreshControl.endRefreshing()
//let newIndexPaths = (1..<tempPosts.count).map { i in
// return IndexPath(row: i, section: 1)
//}
//self.tableView.insertRows(at: newIndexPaths, with: .top)
// self.refreshControl.endRefreshing()
// self.tableView.scrollToRow(at: IndexPath(row:0,section: 0), at: .top, //animated:true)
//self.listenForNewPosts()
//return completion(tempPosts)
//self.posts = tempPosts
//self.tableView.reloadData()
})
}
func fetchPosts(completion: #escaping(_ posts:[Post])->()){
oldPostQuery.queryLimited(toLast: 20).observeSingleEvent(of: .value, with: { (snapshot) in
var tempPosts = [Post]()
let lastPost = self.posts.last
for child in snapshot.children{
if let childSnapshot = child as? DataSnapshot,
let dict = childSnapshot.value as? [String:Any],
let post = Post.parse(childSnapshot.key, dict),
childSnapshot.key != lastPost?.id{
tempPosts.insert(post, at: 0)
}
}
return completion(tempPosts)
//self.posts = tempPosts
//Zself.tableView.reloadData()
})
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
cell.selectionStyle = UITableViewCell.SelectionStyle.none
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 72.0
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height * leadingScreensForBatching {
if !fetchingMore && !endReached {
beginBatchFetch()
}
}
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
func beginBatchFetch(){
fetchingMore = true
fetchPosts{ newPosts in
self.posts.append(contentsOf: newPosts)
self.endReached = newPosts.count == 0
self.fetchingMore = false
UIView.performWithoutAnimation {
self.tableView.reloadData()
self.listenForNewPosts()
}
}
//fetch the post
}
var postListenerHandle:UInt?
func listenForNewPosts(){
guard !fetchingMore else{ return}
//to avoid the listeners twice (duplicate)
stopListeningForNewPosts()
postListenerHandle = newPostQuery.observe(.childAdded) { (snapshot) in
if snapshot.key != self.posts.first?.id {
if let data = snapshot.value as? [String:Any],
//let post = Post.parse(snapshot.key,data)
let _ = Post.parse(snapshot.key,data){
self.stopListeningForNewPosts()
if snapshot.key == self.lastUploadPostID{
self.handleRefresh()
self.lastUploadPostID = nil
}else{
}
}
}
}
}
func stopListeningForNewPosts(){
if let handle = postListenerHandle{
newPostQuery.removeObserver(withHandle: handle)
postListenerHandle = nil
}
}
// func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
//if let navigationController = navigationController as? //ScrollingNavigationController {
// navigationController.showNavbar(animated: true, scrollToTop: true)
//}
// return true
//}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let newPostNavBar = segue.destination as? UINavigationController,
let newPostVC = newPostNavBar.viewControllers[0] as? WritePostViewController{
newPostVC.delegate = self
}
}
}
extension BlogViewController: NewPostVCDelegate{
func didUploadPost(withID id: String) {
print("what up this is the id \(id)")
self.lastUploadPostID = id
}
}
In the storyboard add inside the tableView as last element a transparent view with height equal to 1

Trying to make a like button but Unexpectedly found nil while implicitly unwrapping an Optional value

I try to make a like button and unlike but I it gives me error when I try to press the button in the simulator .if you know any other code for the like button to be much easier will be helpful ( or some websites , yt vids)
#IBOutlet weak var postTextLabel: UILabel!
#IBOutlet weak var subtitleLabel: UILabel!
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var likeLabel : UILabel!
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var unlikeBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
profileImageView.layer.cornerRadius = profileImageView.bounds.height / 2
profileImageView.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
weak var post:Post?
func set(post:Post) {
self.post = post
var postID : String!
self.profileImageView.image = nil
ImageService.getImage(withURL: post.author.photoURL) { image , url in
guard let _post = self.post else {return}
if _post.author.photoURL.absoluteString == url.absoluteString {
self.profileImageView.image = image
}else {
print("not the right image")
}
}
usernameLabel.text = post.author.username
postTextLabel.text = post.text
subtitleLabel.text = post.createdAt.calenderTimeSinceNow()
}
var postID : String!
#IBAction func likePressed(_ sender: Any) {
self.postID = "post_0"
let ref = Database.database().reference()
let keyToPost = ref.child("posts").childByAutoId().key
ref.child("posts").child(self.postID).observeSingleEvent(of: .value) { (snapshot) in
if let post = snapshot.value as? [String : AnyObject] {
let updateLikes : [ String : Any] = [ "peopleWhoLike/\(keyToPost)" : Auth.auth().currentUser!.uid ]
ref.child("posts").child(self.postID).updateChildValues(updateLikes, withCompletionBlock : {(error ,reff) in
if error == nil {
ref.child("posts").child(self.postID).observeSingleEvent(of : .value, with: { (snap) in
if let properties = snap.value as? [ String : AnyObject] {
if let likes = properties["peopleWhoLike"] as? [String: AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
}
}
})
}
})
}
}
}
#IBAction func unlikedPressed(_ sender:Any) {
let ref = Database.database().reference()
ref.child("posts").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
if let properties = snapshot.value as? [String : AnyObject] {
if let peopleWhoLike = properties["peopleWhoLike"] as? [String: AnyObject] {
for (id,person) in peopleWhoLike {
if person as? String == Auth.auth().currentUser!.uid {
ref.child("posts").child(self.postID).child("peopleWhoLike").child(id).removeValue(completionBlock: {(error , reff)in
if error == nil {
ref.child("posts").child(self.postID).observeSingleEvent(of: .value, with: {(snap) in
if let prop = snap.value as? [String : AnyObject] {
if let likes = prop["peopleWhoLike"] as? [String: AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
ref.child("posts").child(self.postID).updateChildValues(["likes" : count])
} else {
self.likeLabel.text = " 0 Likes"
ref.child("posts").child(self.postID).updateChildValues(["likes" : 0])
}
}
})
}
})
self.likeBtn.isHidden = false
self.unlikeBtn.isHidden = true
self.unlikeBtn.isEnabled = true
break
}
}
}
}
})
ref.removeAllObservers()
}
}
class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return posts.count
case 1:
return fetchingMore ? 1 : 0
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row])
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "loadingCell", for: indexPath) as! LoadingCell
cell.spinner.startAnimating()
return cell
}
}
var tableView:UITableView!
var cellHeights: [IndexPath : CGFloat] = [:]
var posts = [Post]()
var fetchingMore = false
var endReached = false
let leadingScreensForBatching:CGFloat = 3.0
var refreshControl:UIRefreshControl!
var seeNewPostsButton:SeeNewPostsButton!
var seeNewPostsButtonTopAnchor:NSLayoutConstraint!
var lastUploadedPostID:String?
var postsRef:DatabaseReference {
return Database.database().reference().child("posts")
}
var oldPostsQuery:DatabaseQuery {
var queryRef:DatabaseQuery
let lastPost = posts.last
if lastPost != nil {
let lastTimestamp = lastPost!.createdAt.timeIntervalSince1970 * 1000
queryRef = postsRef.queryOrdered(byChild: "timestamp").queryEnding(atValue: lastTimestamp)
} else {
queryRef = postsRef.queryOrdered(byChild: "timestamp")
}
return queryRef
}
var newPostsQuery:DatabaseQuery {
var queryRef:DatabaseQuery
let firstPost = posts.first
if firstPost != nil {
let firstTimestamp = firstPost!.createdAt.timeIntervalSince1970 * 1000
queryRef = postsRef.queryOrdered(byChild: "timestamp").queryStarting(atValue: firstTimestamp)
} else {
queryRef = postsRef.queryOrdered(byChild: "timestamp")
}
return queryRef
}
#IBAction func handleLogoutButton(_ sender: Any) {
try! Auth.auth().signOut()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds, style: .plain)
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postCell")
tableView.register(LoadingCell.self, forCellReuseIdentifier: "loadingCell")
tableView.backgroundColor = UIColor(white: 0.90,alpha:1.0)
view.addSubview(tableView)
var layoutGuide:UILayoutGuide!
if #available(iOS 11.0, *) {
layoutGuide = view.safeAreaLayoutGuide
} else {
// Fallback on earlier versions
layoutGuide = view.layoutMarginsGuide
}
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
refreshControl = UIRefreshControl()
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
// Fallback on earlier versions
tableView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
seeNewPostsButton = SeeNewPostsButton()
view.addSubview(seeNewPostsButton)
seeNewPostsButton.translatesAutoresizingMaskIntoConstraints = false
seeNewPostsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
seeNewPostsButtonTopAnchor = seeNewPostsButton.topAnchor.constraint(equalTo: layoutGuide.topAnchor, constant: -44)
seeNewPostsButtonTopAnchor.isActive = true
seeNewPostsButton.heightAnchor.constraint(equalToConstant: 32.0).isActive = true
seeNewPostsButton.widthAnchor.constraint(equalToConstant: seeNewPostsButton.button.bounds.width).isActive = true
seeNewPostsButton.button.addTarget(self, action: #selector(handleRefresh), for: .touchUpInside)
//observePosts()
beginBatchFetch()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
listenForNewPosts()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopListeningForNewPosts()
}
func toggleSeeNewPostsButton(hidden:Bool) {
if hidden {
// hide it
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseOut, animations: {
self.seeNewPostsButtonTopAnchor.constant = -44.0
self.view.layoutIfNeeded()
}, completion: nil)
} else {
// show it
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseOut, animations: {
self.seeNewPostsButtonTopAnchor.constant = 12
self.view.layoutIfNeeded()
}, completion: nil)
}
}
#objc func handleRefresh() {
print("Refresh!")
toggleSeeNewPostsButton(hidden: true)
newPostsQuery.queryLimited(toFirst: 20).observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [Post]()
let firstPost = self.posts.first
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
let post = Post.parse(childSnapshot.key, data),
childSnapshot.key != firstPost?.id {
tempPosts.insert(post, at: 0)
}
}
self.posts.insert(contentsOf: tempPosts, at: 0)
let newIndexPaths = (0..<tempPosts.count).map { i in
return IndexPath(row: i, section: 0)
}
self.refreshControl.endRefreshing()
self.tableView.insertRows(at: newIndexPaths, with: .top)
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
self.listenForNewPosts()
})
}
func fetchPosts(completion:#escaping (_ posts:[Post])->()) {
oldPostsQuery.queryLimited(toLast: 20).observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [Post]()
let lastPost = self.posts.last
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
let post = Post.parse(childSnapshot.key, data),
childSnapshot.key != lastPost?.id {
tempPosts.insert(post, at: 0)
}
}
return completion(tempPosts)
})
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height * leadingScreensForBatching {
if !fetchingMore && !endReached {
beginBatchFetch()
}
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 72.0
}
func beginBatchFetch() {
fetchingMore = true
self.tableView.reloadSections(IndexSet(integer: 1), with: .fade)
fetchPosts { newPosts in
self.posts.append(contentsOf: newPosts)
self.fetchingMore = false
self.endReached = newPosts.count == 0
UIView.performWithoutAnimation {
self.tableView.reloadData()
self.listenForNewPosts()
}
}
}
var postListenerHandle:UInt?
func listenForNewPosts() {
guard !fetchingMore else { return }
// Avoiding duplicate listeners
stopListeningForNewPosts()
postListenerHandle = newPostsQuery.observe(.childAdded, with: { snapshot in
if snapshot.key != self.posts.first?.id,
let data = snapshot.value as? [String:Any],
let post = Post.parse(snapshot.key, data) {
self.stopListeningForNewPosts()
if snapshot.key == self.lastUploadedPostID {
self.handleRefresh()
self.lastUploadedPostID = nil
} else {
self.toggleSeeNewPostsButton(hidden: false)
}
}
})
}
func stopListeningForNewPosts() {
if let handle = postListenerHandle {
newPostsQuery.removeObserver(withHandle: handle)
postListenerHandle = nil
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let newPostNavBar = segue.destination as? UINavigationController,
let newPostVC = newPostNavBar.viewControllers[0] as? NewPostViewController {
newPostVC.delegate = self
}
}
}
extension HomeController: NewPostVCDelegate {
func didUploadPost(withID id: String) {
self.lastUploadedPostID = id
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
I try to make a like button and unlike but I it gives me error when I try to press the button in the simulator .if you know any other code for the like button to be much easier will be helpful ( or some websites , yt vids)
On this line
ref.child("posts").child(self.postID)
the postID is undefined which is causing the crash. In the comments you state you assign a value to it, but the code in the question doesn't include how that's being done so there's a high likelyhood that value is not being assigned.
The fix would be to assign a value to that class var before that line, like this
#IBAction func likePressed(_ sender: Any) {
self.postID = "post_0" //or however you determine which post it is
// e.g. self.postID = getCurrentPostId()
let ref = Database.database().reference()
let keyToPost = ref.child("posts").childByAutoId().key
ref.child("posts").child(self.postID)...
You may also want to implement some basic error checking as well to ensure the postID is not nil before trying to call the Firebase function.
if let postID = getCurrentPostID() {
//perform the firebase function using postID
} else {
//display an error 'not post selected'
}