I have recently encountered the Apple Mach-O Linker Error. Most guides suggest to change bitcode in Build Settings to "No", however it only applies to the ld error, which is different from mine. I will provide a screenshot, please help to fix the bug.
The pod HandySwift is causing the bugs to appear.
Here is the Github code source for it. https://github.com/Flinesoft/HandySwift
It is from the CSV Importer pod on Cocoapods.
https://cocoapods.org/pods/CSVImporter
Click here to look at the overview of the HandySwift files
Click here for the screenshot of the error
Code referencing the error:
"static (extension in HandySwift):Swift.Double.seconds(Swift.Double) -> Swift.Double", referenced from:
//
// CalendarViewController.swift
// DBS
//
// Created by SDG on 18/10/2017.
// Copyright © 2017 DBSSDG. All rights reserved.
//
import UIKit
import JTAppleCalendar
import CSVImporter
import SystemConfiguration
enum EventTypes{
case SE,PH,SH
}
struct events{
var Title : String
var StartDate : Date
var EndDate : Date
var EventType : EventTypes
}
var PassingEvent = ("G7-G11 Mid-year Exam (4-19)", Date(), Date(), EventTypes.SE)
var TodayEvent = [events] ()
extension Array where Element:Equatable {
func removeDuplicates() -> [Element] {
var result = [Element]()
for value in self {
if result.contains(value) == false {
result.append(value)
}
}
return result
}
}
class CalendarViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIViewControllerPreviewingDelegate {
#IBOutlet weak var CalendarView: JTAppleCalendarView!
#IBOutlet weak var CalendarStackView: UIStackView!
#IBOutlet weak var EventsTableView: UITableView!
#IBOutlet weak var StackView: UIStackView!
#IBOutlet weak var year: UILabel!
#IBOutlet weak var month: UILabel!
#IBOutlet weak var todayButton: UIButton!
var didScroll = false
#IBAction func TodayButton(_ sender: Any) {
CalendarView.scrollToDate(Date())
CalendarView.deselectAllDates()
CalendarView.selectDates([Date()])
EventsTableView.reloadData()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(0.5), execute: {
self.CalendarView.selectDates([Date()])
self.EventsTableView.reloadData()
})
}
var index = 0
var CurrentDayEventsArray = [(Date, events)] ()
var CurrentDay = Date()
var DayEvents = [(Date, events)] ()
var SEBlue = UIColor(red: 97.0/255.0, green: 142.0/255.0, blue: 249.0/255.0, alpha: 1)
var SHOrange = UIColor(red: 1, green: 142.0/255.0, blue: 80.0/255.0, alpha: 1)
var currentmonth : String = ""
let formatter : DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = Calendar.current.timeZone
dateFormatter.locale = Calendar.current.locale
dateFormatter.dateFormat = "yyyy MM dd"
return dateFormatter
}()
#IBOutlet weak var DaysStack: UIStackView!
func WillAddCalendar(acrion: UIAlertAction){
let StringURL = "https://calendar.google.com/calendar/ical/g.dbs.edu.hk_tdmjqqq8vlv8keepi7a65f7j7s%40group.calendar.google.com/public/basic.ics"
let url = URL(string: StringURL)!
if isInternetAvailable(){
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}else{
let networkAlert = UIAlertController(title: "ERROR", message: "Please check your network availability.", preferredStyle: .alert)
networkAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(networkAlert, animated: true)
}
}
func ShareCalendar(action: UIAlertAction){
let StringURL = "https://calendar.google.com/calendar/ical/g.dbs.edu.hk_tdmjqqq8vlv8keepi7a65f7j7s%40group.calendar.google.com/public/basic.ics"
let url = URL(string: StringURL)!
let ActivityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
present(ActivityController, animated: true, completion: nil)
}
func ActionSheetFunc(){
//Actions
let AddCalendarAction = UIAlertAction(title: "Add Calendar to Phone", style: .default, handler: WillAddCalendar)
let ShareAction = UIAlertAction(title: "Share Calendar", style: .default, handler: ShareCalendar)
let CancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
//Action Sheet
let ActionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
ActionSheet.addAction(AddCalendarAction)
ActionSheet.addAction(ShareAction)
ActionSheet.addAction(CancelAction)
present(ActionSheet, animated: true)
}
func AllEvents(){
performSegue(withIdentifier: "Calendar to All Events", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Calendar"
setUpCalendarView()
ParseCSV()
//UI Set up
year.frame = CGRect(x: 16, y: self.view.frame.height * 0.125, width: 100, height: 30)
todayButton.frame = CGRect(x: self.view.frame.width - todayButton.frame.size.width - 16, y: year.frame.origin.y, width: 0, height: 30)
todayButton.sizeToFit()
month.frame = CGRect(x: 16, y: year.frame.origin.y + year.frame.size.height, width: self.view.frame.width, height: 30)
CalendarView.frame.size.width = self.view.frame.width
CalendarView.frame.size.height = self.view.frame.height * 0.425
CalendarView.sizeToFit()
CalendarStackView.frame.origin.y = self.view.frame.height * 0.25
CalendarStackView.frame.origin.x = 0
CalendarStackView.frame.size.width = self.view.frame.width
CalendarStackView.frame.size.height = DaysStack.frame.height + CalendarView.frame.height
StackView.frame = CalendarStackView.frame
EventsTableView.frame.origin.y = CalendarStackView.frame.origin.y + CalendarStackView.frame.size.height
EventsTableView.frame.origin.x = 0
EventsTableView.frame.size.width = self.view.frame.width
EventsTableView.frame.size.height = self.view.frame.height - EventsTableView.frame.origin.y
EventsTableView.isScrollEnabled = true
self.registerForPreviewing(with: self, sourceView: EventsTableView)
//let EventsTableViewBottomConstraint = NSLayoutConstraint(item: EventsTableView, attribute: .bottomMargin, relatedBy: .equal, toItem: self.view, attribute: .bottomMargin, multiplier: 1, constant: 0)
//NSLayoutConstraint.activate([EventsTableViewBottomConstraint])
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = .never
}
EventsArray = [events]()
TodayEvent = [events]()
CurrentDayEventsArray = [(Date, events)]()
CalendarView.scrollToDate(Date())
TodayButton(self)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let AddCalendar = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(ActionSheetFunc))
let AllEvents = UIBarButtonItem(title: "All Events", style: .plain, target: self, action: #selector(self.AllEvents))
self.navigationItem.rightBarButtonItems = [AddCalendar, AllEvents]
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
CalendarView.selectDates([Date()])
}
func ParseCSV (){
let path = Bundle.main.path(forResource: "2017 - 2018 School Events New", ofType: "csv")!
let importer = CSVImporter<[String: String]>(path: path)
importer.startImportingRecords(structure: { (headerValues) -> Void in
}) { $0 }.onFinish { importedRecords in
for record in importedRecords {
self.formatter.dateFormat = "d/M/yyyy"
let EventStartDate = self.formatter.date(from: record["Start Date"]!)
let EventEndDate = self.formatter.date(from: record["End Date"]!)
let string = record["Title"]!
let input = string
var output = ""
var didColon = false
for i in input{
if didColon{
output += "\(i)"
}
if i == Character(":"){
didColon = true
}
}
output.removeFirst()
switch record["Type"]! {
case "PH" :
EventsArray += [events(Title: output, StartDate: EventStartDate!, EndDate: EventEndDate!, EventType: .PH)]
if EventStartDate! <= Date() && EventEndDate! >= Date(){
TodayEvent += [events(Title: output, StartDate: EventStartDate!, EndDate: EventEndDate!, EventType: .PH)]
}
case "SH" :
EventsArray += [events(Title: output, StartDate: EventStartDate!, EndDate: EventEndDate!, EventType: .SH)]
if EventStartDate! <= Date() && EventEndDate! >= Date(){
TodayEvent += [events(Title: output, StartDate: EventStartDate!, EndDate: EventEndDate!, EventType: .SH)]
}
case "SE" :
EventsArray += [events(Title: output, StartDate: EventStartDate!, EndDate: EventEndDate!, EventType: .SE)]
if EventStartDate! <= Date() && EventEndDate! >= Date(){
TodayEvent += [events(Title: output, StartDate: EventStartDate!, EndDate: EventEndDate!, EventType: .SE)]
}
default:
print("ERROR")
}
}
self.ParseAdoptionTimetable()
for i in TodayEvent{
self.CurrentDayEventsArray += [(Date(), i)]
}
self.EventsTableView.reloadData()
}
}
func ParseAdoptionTimetable(){
// if let url = URL(string: "http://www.dbs.edu.hk/index.php?section=calendar&listall=1") {
// do {
// let html = try String(contentsOf: url)
// for i in html.split(separator: ">") {
// if i.components(separatedBy: " adopts ").count == 2 {
// let String = (i.split(separator: "<")[0])
// print(String)
// //events(Title: i.split(separator: "<")[0], StartDate: <#T##Date#>, EndDate: <#T##Date#>, EventType: <#T##EventTypes#>)
// }
// }
// }catch{
// print("ERROR")
// }
// }
let Formatter = DateFormatter()
Formatter.dateFormat = "dd MM yyyy"
EventsArray += [events(Title: "30/4 Mon Adopts Tue Timetable", StartDate: Formatter.date(from: "30 04 2018")!, EndDate: Formatter.date(from: "30 04 2018")!, EventType: .SE), events(Title: "14/5 Mon Adopts Fri Timetable", StartDate: Formatter.date(from: "14 05 2018")!, EndDate: Formatter.date(from: "14 05 2018")!, EventType: .SE)]
EventsArray = EventsArray.sorted(by: { $0.StartDate <= $1.StartDate })
}
func setUpCalendarView(){
// Set up calendar spacing
CalendarView.minimumLineSpacing = 0
CalendarView.minimumInteritemSpacing = 0
// Set up labels
CalendarView.visibleDates{(visibleDates) in
let date = visibleDates.monthDates.first!.date
self.formatter.dateFormat = "yyyy"
self.year.text = self.formatter.string(from: date)
if self.year.text == self.formatter.string(from: Date()){
self.year.textColor = UIColor.red
} else {
self.year.textColor = UIColor.black
}
self.formatter.dateFormat = "MMMM"
self.month.text = self.formatter.string(from: date)
self.currentmonth = self.formatter.string(from: date)
if self.month.text == self.formatter.string(from: Date()){
self.month.textColor = UIColor.red
} else {
self.month.textColor = UIColor.black
}
}
}
func handleCellTextColor(view: JTAppleCell?, cellState: CellState) {
guard let validCell = view as? CustomCell else { return }
if cellState.isSelected {
validCell.selectedView.isHidden = false
} else {
validCell.selectedView.isHidden = true
}
}
func LoadEvents(view: JTAppleCell?, cellState: CellState) -> Any {
guard let validCell = view as? CustomCell else {return "Load Events Error"}
let CellDate = cellState.date
var CellDateEventsArray = [(Date, events)] ()
for event in EventsArray{
let EventStartDate = event.StartDate
let EventEndDate = event.EndDate
if CellDate >= EventStartDate && CellDate <= EventEndDate{
CellDateEventsArray += [(CellDate, event)]
}
}
CurrentDayEventsArray = CellDateEventsArray
if cellState.date == Date(){
self.EventsTableView.reloadData()
}
//CurrentDate = cellState.date
return CellDateEventsArray
}
func handleCellSelected(view: JTAppleCell?, cellState: CellState) {
guard let validCell = view as? CustomCell else { return }
var isPublicHoliday = false
for i in CurrentDayEventsArray{
if i.1.EventType == .PH{
isPublicHoliday = true
}
}
if cellState.isSelected {
validCell.datelabel.textColor = UIColor.white
}else{
if cellState.dateBelongsTo == .thisMonth{
//validCell.datelabel.textColor = UIColor.init(red: 253/255.0, green: 114/255.0, blue: 116.0/255.0, alpha: 1.0)
if cellState.day == .sunday || isPublicHoliday{
validCell.datelabel.textColor = UIColor.red
}else{
validCell.datelabel.textColor = UIColor.black
}
validCell.isUserInteractionEnabled = true
}else{
validCell.datelabel.textColor = UIColor.lightGray
//validCell.isUserInteractionEnabled = false
}
}
}
func setUpViewsForCalendar(from visibleDates: DateSegmentInfo){
let date = visibleDates.monthDates.first!.date
self.formatter.dateFormat = "yyyy"
self.year.text = self.formatter.string(from: date)
self.formatter.dateFormat = "MMMM"
self.month.text = self.formatter.string(from: date)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count = 0
for _ in CurrentDayEventsArray{
count += 1
}
if count == 0{
return 1
}
return count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
DayEvents = CurrentDayEventsArray
let cell = tableView.dequeueReusableCell(withIdentifier: "CalendarEventCell")! as UITableViewCell
cell.isUserInteractionEnabled = true
if CurrentDayEventsArray.isEmpty{
cell.textLabel?.text = "No events"
cell.textLabel?.textAlignment = .center
cell.detailTextLabel?.text = ""
cell.imageView?.image = nil
cell.isUserInteractionEnabled = false
cell.accessoryType = .none
return cell
}
//Manage Date
let StartDate = CurrentDayEventsArray[indexPath.row].1.StartDate
let EndDate = CurrentDayEventsArray[indexPath.row].1.EndDate
self.formatter.dateFormat = "d/M"
let StartDateString = formatter.string(from: StartDate)
let EndDateString = formatter.string(from: EndDate)
//Image
let EventType = CurrentDayEventsArray[indexPath.row].1.EventType
if let image = UIImage(named: "dot"){
let tintableImage = image.withRenderingMode(.alwaysTemplate)
cell.imageView?.image = tintableImage
}
switch EventType {
case .PH:
cell.imageView?.tintColor = UIColor.red
case .SH:
cell.imageView?.tintColor = SHOrange
case .SE:
cell.imageView?.tintColor = SEBlue
default:
cell.imageView?.tintColor = UIColor.red
}
//Title
cell.textLabel?.adjustsFontSizeToFitWidth = true
cell.textLabel?.text = String(describing: CurrentDayEventsArray[indexPath.row].1.Title)
//Subtitle
let Subtitle = "\(StartDateString) - \(EndDateString)"
if StartDate != EndDate{
cell.detailTextLabel?.text = Subtitle
}else{
cell.detailTextLabel?.text = StartDateString
}
//Arrow
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
//cell.selectionStyle = .gray
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
index = indexPath.row
PassingEvent = (DayEvents[index].1.Title, DayEvents[index].1.StartDate, DayEvents[index].1.EndDate, DayEvents[index].1.EventType)
performSegue(withIdentifier: "Detail Event", sender: self)
}
/*
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let DestViewController = segue.destination as! DetailedEventViewController
DestViewController.PassingEvent = (CurrentDayEventsArray[index].1.Title, CurrentDayEventsArray[index].1.StartDate, CurrentDayEventsArray[index].1.EndDate, CurrentDayEventsArray[index].1.EventType)
}
*/
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = EventsTableView.indexPathForRow(at: location) else {
return nil
}
previewingContext.sourceRect = EventsTableView.cellForRow(at: indexPath)!.frame
let index = indexPath.row
PassingEvent = (DayEvents[index].1.Title, DayEvents[index].1.StartDate, DayEvents[index].1.EndDate, DayEvents[index].1.EventType)
let destViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Detail Event") as! DetailedEventViewController
return destViewController
return nil
}
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
navigationController?.pushViewController(viewControllerToCommit, animated: true)
}
func isInternetAvailable() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
}
}
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
return false
}
let isReachable = flags.contains(.reachable)
let needsConnection = flags.contains(.connectionRequired)
return (isReachable && !needsConnection)
}
}
extension CalendarViewController: JTAppleCalendarViewDelegate, JTAppleCalendarViewDataSource {
func calendar(_ calendar: JTAppleCalendarView, willDisplay cell: JTAppleCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) {
EventsTableView.reloadData()
}
func configureCalendar(_ calendar: JTAppleCalendarView) -> ConfigurationParameters {
formatter.dateFormat = "yyy MM dd"
formatter.timeZone = Calendar.current.timeZone
formatter.locale = Calendar.current.locale
let startDate = formatter.date(from: "2017 09 01")
let endDate = formatter.date(from: "2019 08 31")
let generateInDates: InDateCellGeneration = .forAllMonths
let generateOutDates: OutDateCellGeneration = .tillEndOfGrid
let firstDayOfWeek: DaysOfWeek = .sunday
let parameters = ConfigurationParameters(startDate: startDate!, endDate: endDate!, numberOfRows: 4, calendar: Calendar.current, generateInDates: generateInDates, generateOutDates: generateOutDates, firstDayOfWeek: firstDayOfWeek, hasStrictBoundaries: true)
//let parameters = ConfigurationParameters(startDate: startDate!, endDate: endDate!)
return parameters
}
func calendar(_ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell {
let CalendarCell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
CalendarCell.datelabel.text = cellState.text
CalendarCell.selectedView.layer.cornerRadius = CalendarCell.selectedView.frame.width/2
CalendarCell.backgroundColor = UIColor.white
CalendarCell.EventCircle.layer.cornerRadius = CalendarCell.EventCircle.frame.width / 2
CalendarCell.EventCircle.isHidden = true
CalendarCell.SchoolHolidayBar.backgroundColor = self.SHOrange
CalendarCell.SchoolHolidayBar.isHidden = true
CalendarCell.isUserInteractionEnabled = false
if cellState.dateBelongsTo == .thisMonth{
CalendarCell.isSelected = false
}
LoadEvents(view: CalendarCell, cellState: cellState)
for i in CurrentDayEventsArray{
if i.1.EventType == .SH{
CalendarCell.SchoolHolidayBar.isHidden = true
CalendarCell.EventCircle.backgroundColor = SHOrange
CalendarCell.EventCircle.isHidden = false
break
}else if i.1.EventType == .PH{
CalendarCell.datelabel.textColor = UIColor.red
}else if i.1.EventType == .SE{
CalendarCell.EventCircle.backgroundColor = UIColor.lightGray
CalendarCell.EventCircle.isHidden = false
}else{
}
}
if cellState.date == Date(){
LoadEvents(view: CalendarCell, cellState: cellState)
EventsTableView.reloadData()
}
handleCellTextColor(view: CalendarCell, cellState: cellState)
handleCellSelected(view: CalendarCell, cellState: cellState)
return CalendarCell
}
func calendar(_ calendar: JTAppleCalendarView, didSelectDate date: Date, cell: JTAppleCell?, cellState: CellState) {
if didScroll{
calendar.deselect(dates: [CurrentDay])
didScroll = false
}
CurrentDay = date
//calendar.deselectAllDates()
//calendar.selectDates([date])
handleCellTextColor(view: cell, cellState: cellState)
handleCellSelected(view: cell, cellState: cellState)
LoadEvents(view: cell, cellState: cellState)
EventsTableView.reloadData()
}
func calendar(_ calendar: JTAppleCalendarView, didDeselectDate date: Date, cell: JTAppleCell?, cellState: CellState) {
handleCellTextColor(view: cell, cellState: cellState)
handleCellSelected(view: cell, cellState: cellState)
//calendar.deselect(dates: [date])
}
func calendar(_ calendar: JTAppleCalendarView, didScrollToDateSegmentWith visibleDates: DateSegmentInfo) {
let date = visibleDates.monthDates.first!.date
formatter.dateFormat = "yyyy"
year.text = self.formatter.string(from: date)
formatter.dateFormat = "MMMM"
month.text = self.formatter.string(from: date)
setUpCalendarView()
}
func calendarDidScroll(_ calendar: JTAppleCalendarView) {
didScroll = true
}
}
//}
In your TodayButton(_:) instead of:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(0.5), execute: {
//...
})
do:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
//...
})
That .seconds(Int) takes an Int. So passing a Double in it by doing .seconds(0.5) makes it ambiguous as there is no .seconds(Double) in the DispatchTimeInterval enum.
To achieve this 0.5 second delay you can either do it in 2 ways:
+ 0.5
+ .milliseconds(500)
Related
I have a custom view controller class which presents an alert controller when it receives a not connected notification from my network services class. I want to dismiss the alert controller when I receive a further connected notification from the network services class but I am unable to dismiss the presenting alert.
Here is my custom view controller class;
class CompanyPriceListVC: UITableViewController {
// MARK: - Properties
let companyStockSymbols = ["AAPL","AMZN", "MSFT", "GOOGL", "TSLA", "META","COINBASE:BTC", "BINANCE:BTCUSDT", "BINANCE:BNBBTC", "IC MARKETS:1", "IC MARKETS:2"]
let defaultStockInfo = StockInfo(tradeConditions: nil, price: 0.00, symbol: "-", timestamp: 0.0, volume: 0.0)
lazy var companyStockInfo = [StockInfo](repeating: defaultStockInfo, count: companyStockSymbols.count)
var companyDetailsVC:CompanyDetailsVC?
var tableRowSelected: Int?
let defaultPriceHistory = PriceHistory(previous: nil, relative: 0.0, absolute: 0.0)
lazy var priceHistory = [PriceHistory](repeating: defaultPriceHistory , count: companyStockSymbols.count)
var deviceOrientation: Orientation = .portrait
let activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.style = .large
activityIndicator.hidesWhenStopped = true
activityIndicator.color = .white
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.startAnimating()
return activityIndicator
}()
let isConnected = Notification.Name(rawValue: isConnectedNotificationKey)
let isNotConnected = Notification.Name(rawValue: isDisconnectedNotificationKey)
let alertController = UIAlertController(title: "Connectivity Error", message: "Network connection lost. Please check your WiFi settings, mobile data settings or reception coverage.", preferredStyle: .alert)
// MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
if UIDevice.current.orientation.isPortrait {
self.deviceOrientation = .portrait
self.tableView.register(CompanyStockCellPortrait.self, forCellReuseIdentifier: "CompanyStockCellPortrait")
} else {
self.deviceOrientation = .landscape
self.tableView.register(CompanyStockCellLandscape.self, forCellReuseIdentifier: "CompanyStockCellLandscape")
}
configureNavigationBar()
view.backgroundColor = .black
configureTableView()
configureConstraints()
createObservers()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
private func createObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(CompanyPriceListVC.dismissAndFetchStockInfo), name: isConnected, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(CompanyPriceListVC.notifyUserOnScreen(notification:)), name: isNotConnected, object: nil)
}
#objc private func fetchStockInfo() {
NetworkServices.sharedInstance.fetchStockInfo(symbols: companyStockSymbols, delegate: self)
}
#objc private func notifyUserOnScreen(notification: NSNotification) {
self.present(alertController, animated: true)
}
#objc public func dismissAndFetchStockInfo() {
print("DEBUG: isBeingPresented: \(alertController.isBeingPresented)")
if alertController.isBeingPresented {
self.alertController.dismiss(animated: true, completion: nil)
}
fetchStockInfo()
}
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
coordinator.animate(alongsideTransition: { (context) in
guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }
if windowInterfaceOrientation.isPortrait {
self.deviceOrientation = .portrait
self.tableView.register(CompanyStockCellPortrait.self, forCellReuseIdentifier: "CompanyStockCellPortrait")
self.tableView.reloadData()
} else {
self.deviceOrientation = .landscape
self.tableView.register(CompanyStockCellLandscape.self, forCellReuseIdentifier: "CompanyStockCellLandscape")
self.tableView.reloadData()
}
})
}
private var windowInterfaceOrientation: UIInterfaceOrientation? {
return self.view.window?.windowScene?.interfaceOrientation
}
// MARK: - TableView Data Source Methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return companyStockInfo.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch deviceOrientation {
case .portrait:
let tableViewCellPortrait = tableView.dequeueReusableCell(withIdentifier: "CompanyStockCellPortrait", for: indexPath) as! CompanyStockCellPortrait
tableViewCellPortrait.symbolValue.text = companyStockInfo[indexPath.row].symbol
var latestPrice = companyStockInfo[indexPath.row].price
latestPrice = round(latestPrice * 100.0) / 100.00
tableViewCellPortrait.priceValue.text = String(format: "%.2f", arguments:[latestPrice])
if let _ = priceHistory[indexPath.row].previous {
let absoluteDifference = priceHistory[indexPath.row].absolute
let relativeDifference = priceHistory[indexPath.row].relative
if absoluteDifference > 0.0 {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.up.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .systemGreen
tableViewCellPortrait.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellPortrait.relativePriceDiffValue.textColor = .systemGreen
tableViewCellPortrait.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellPortrait.absolutePriceDiffValue.textColor = .systemGreen
} else if absoluteDifference < 0.0 {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.down.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .systemRed
tableViewCellPortrait.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellPortrait.relativePriceDiffValue.textColor = .systemRed
tableViewCellPortrait.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellPortrait.absolutePriceDiffValue.textColor = .systemRed
} else {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "diamond.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .white
tableViewCellPortrait.relativePriceDiffValue.text = "0.00%"
tableViewCellPortrait.relativePriceDiffValue.textColor = .white
tableViewCellPortrait.absolutePriceDiffValue.text = "(0.00)"
tableViewCellPortrait.absolutePriceDiffValue.textColor = .white
}
} else {
tableViewCellPortrait.priceDirectionImageView.image = UIImage()
tableViewCellPortrait.priceDirectionImageView.tintColor = .white
tableViewCellPortrait.relativePriceDiffValue.text = ""
tableViewCellPortrait.absolutePriceDiffValue.text = ""
}
return tableViewCellPortrait
case .landscape:
let tableViewCellLandscape = tableView.dequeueReusableCell(withIdentifier: "CompanyStockCellLandscape", for: indexPath) as! CompanyStockCellLandscape
tableViewCellLandscape.symbolValue.text = companyStockInfo[indexPath.row].symbol
var latestPrice = companyStockInfo[indexPath.row].price
latestPrice = round(latestPrice * 100.0) / 100.00
tableViewCellLandscape.priceValue.text = String(format: "%.2f", arguments:[latestPrice])
if let _ = priceHistory[indexPath.row].previous {
let absoluteDifference = priceHistory[indexPath.row].absolute
let relativeDifference = priceHistory[indexPath.row].relative
if absoluteDifference > 0.0 {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.up.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .systemGreen
tableViewCellLandscape.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellLandscape.relativePriceDiffValue.textColor = .systemGreen
tableViewCellLandscape.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellLandscape.absolutePriceDiffValue.textColor = .systemGreen
} else if absoluteDifference < 0.0 {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.down.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .systemRed
tableViewCellLandscape.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellLandscape.relativePriceDiffValue.textColor = .systemRed
tableViewCellLandscape.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellLandscape.absolutePriceDiffValue.textColor = .systemRed
} else {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "diamond.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .white
tableViewCellLandscape.relativePriceDiffValue.text = "0.00%"
tableViewCellLandscape.relativePriceDiffValue.textColor = .white
tableViewCellLandscape.absolutePriceDiffValue.text = "(0.00)"
tableViewCellLandscape.absolutePriceDiffValue.textColor = .white
}
} else {
tableViewCellLandscape.priceDirectionImageView.image = UIImage()
tableViewCellLandscape.priceDirectionImageView.tintColor = .white
tableViewCellLandscape.relativePriceDiffValue.text = ""
tableViewCellLandscape.absolutePriceDiffValue.text = ""
}
return tableViewCellLandscape
}
}
// MARK: - TableView Delegate Methods
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
companyDetailsVC = CompanyDetailsVC(companyStockInfo: companyStockInfo[indexPath.row])
tableRowSelected = indexPath.row
navigationController?.pushViewController(companyDetailsVC!, animated: true)
}
// MARK: - Helper Functions
private func configureNavigationBar() {
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navigationController?.navigationBar.topItem?.title = "Trade Tracker"
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.prefersLargeTitles = true
}
private func configureTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .singleLine
tableView.separatorColor = .white
tableView.tableFooterView = UIView()
}
private func configureConstraints() {
tableView.addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
activityIndicator.topAnchor.constraint(equalTo: tableView.topAnchor, constant: 250)
])
tableView.layoutSubviews()
}
}
// MARK: - Network Service Delegate Method
extension CompanyPriceListVC: NetworkServicesDelegate {
func sendStockInfo(stocksInfo: [String : StockInfo]) {
DispatchQueue.main.async {
// Compute absolute and relative price differences
for (index, symbol) in self.companyStockSymbols.enumerated() {
if stocksInfo[symbol] != nil {
self.priceHistory[index].previous = self.companyStockInfo[index].price
self.companyStockInfo[index] = stocksInfo[symbol]!
var latestPrice = self.companyStockInfo[index].price
latestPrice = round(latestPrice * 100.0) / 100.00
if let previous = self.priceHistory[index].previous {
let previousPrice = round(previous * 100.0) / 100.0
var absoluteDifference = latestPrice - previousPrice
absoluteDifference = round(absoluteDifference * 100.0) / 100.0
self.priceHistory[index].absolute = absoluteDifference
var relativeDifference = 0.0
if previousPrice != 0.00 {
relativeDifference = (absoluteDifference / previousPrice) * 100.0
relativeDifference = round(relativeDifference * 100) / 100.0
}
self.priceHistory[index].relative = relativeDifference
}
}
}
self.tableView.reloadData()
self.activityIndicator.stopAnimating()
guard let rowSelected = self.tableRowSelected else { return }
self.companyDetailsVC!.companyStockInfo = self.companyStockInfo[rowSelected]
self.companyDetailsVC!.relativeDifference = self.priceHistory[rowSelected].relative
self.companyDetailsVC!.absoluteDifference = self.priceHistory[rowSelected].absolute
}
}
}
and here is my network services class;
import UIKit
import Starscream
import Network
protocol NetworkServicesDelegate: AnyObject {
func sendStockInfo(stocksInfo: [String: StockInfo])
}
final class NetworkServices {
static let sharedInstance = NetworkServices()
var request = URLRequest(url: FINNHUB_SOCKET_STOCK_INFO_URL!)
var socket: WebSocket!
public private(set) var isConnected = false
var stocksInfo: [String: StockInfo] = [:]
var socketResults: [String: [StockInfo]] = [:]
weak var delegate: NetworkServicesDelegate?
var stockSymbols: [String] = []
private init() {
request.timeoutInterval = 5
socket = WebSocket(request: request)
socket.delegate = self
}
private let queue = DispatchQueue.global()
private let monitor = NWPathMonitor()
public func startMonitoring() {
monitor.start(queue: queue)
self.monitor.pathUpdateHandler = { [weak self] path in
if path.status == .satisfied {
// connect the socket
self?.socket.connect()
} else {
// disconnect the socket
self?.socket.disconnect()
// post notification that socket is now disconnected
DispatchQueue.main.async {
let name = Notification.Name(rawValue: isDisconnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
}
}
}
}
public func stopMonitoring() {
monitor.cancel()
}
func fetchStockInfo(symbols: [String], delegate: CompanyPriceListVC) {
stockSymbols = symbols
self.delegate = delegate
for symbol in symbols {
let string = FINNHUB_SOCKET_MESSAGE_STRING + symbol + "\"}"
socket.write(string: string)
}
}
private func parseJSONSocketData(_ socketString: String) {
self.socketResults = [:]
self.stocksInfo = [:]
let decoder = JSONDecoder()
do {
let socketData = try decoder.decode(SocketData.self, from: socketString.data(using: .utf8)!)
guard let stockInfoData = socketData.data else { return }
for stockInfo in stockInfoData {
let symbol = stockInfo.symbol
if self.socketResults[symbol] == nil {
self.socketResults[symbol] = [StockInfo]()
}
self.socketResults[symbol]?.append(stockInfo)
}
for (symbol, stocks) in self.socketResults {
for item in stocks {
if self.stocksInfo[symbol] == nil {
self.stocksInfo[symbol] = item
} else if item.timestamp > self.stocksInfo[symbol]!.timestamp {
self.stocksInfo[symbol] = item
}
}
}
self.delegate?.sendStockInfo(stocksInfo: self.stocksInfo)
} catch {
print("DEBUG: error: \(error.localizedDescription)")
}
}
func fetchCompanyInfo(symbol: String, completion: #escaping (CompanyInfo?, UIImage?)->()) {
let urlString = FINNHUB_HTTP_COMPANY_INFO_URL_STRING + symbol + "&token=" + FINNHUB_API_TOKEN
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error fetching company info: \(error)")
}
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let companyInfo = try decoder.decode(CompanyInfo.self, from: data)
guard let logoURL = URL(string: companyInfo.logo) else { return }
let task = URLSession.shared.dataTask(with: logoURL) { data, response, error in
if let error = error {
print("Error fetching logo image: \(error)")
}
guard let data = data else { return }
guard let logoImage = UIImage(data: data) else { return }
completion(companyInfo, logoImage)
}
task.resume()
} catch {
print("Error decoding JSON: \(error)")
completion(nil, nil)
}
}
task.resume()
}
}
extension NetworkServices: WebSocketDelegate {
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(_):
self.isConnected = true
// post notification that socket is now connected
let name = Notification.Name(rawValue: isConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
case .disconnected(let reason, let code):
print("DEBUG: Got disconnected.")
self.isConnected = false
case .cancelled:
print("DEBUG: cancelled.")
// post notification that socket is not connected
case .reconnectSuggested(let suggestReconnect):
print("DEBUG: suggestReconnect = \(suggestReconnect)")
// post notification that socket is not connected
case .viabilityChanged(let viabilityChanged):
print("DEBUG: viabilityChange = \(viabilityChanged)")
case .error(let error):
print("DEBUG error: \(String(describing: error?.localizedDescription))")
case .text(let socketString):
parseJSONSocketData(socketString)
default:
break
}
}
}
I've tried querying the isBeingPresented property of the alert controller but it always tests as false even though I can see the alert controller is being presented.
You could do a check if the currently presented UIViewController is actually the alertController like this:
#objc public func dismissAndFetchStockInfo() {
if presentedViewController == alertController {
alertController.dismiss(animated: true, completion: nil)
}
fetchStockInfo()
}
This is because isBeingPresented is only valid inside the view[Will|Did][Disa|A]ppear methods.
I am using two collectionViews in my view controller. 1 has a custom layout and custom attributes for its supplementary views.
Here is the view controller:
class ProgressViewController: UIViewController {
private lazy var data = fetchData()
private lazy var recordsDataSource = makeRecordsDataSource()
private lazy var timelineDataSource = makeTimelineDataSource()
fileprivate typealias RecordsDataSource = UICollectionViewDiffableDataSource<YearMonthDay, TestRecord>
fileprivate typealias RecordsDataSourceSnapshot = NSDiffableDataSourceSnapshot<YearMonthDay, TestRecord>
fileprivate typealias TimelineDataSource = UICollectionViewDiffableDataSource<TimelineSection,YearMonthDay>
fileprivate typealias TimelineDataSourceSnapshot = NSDiffableDataSourceSnapshot<TimelineSection,YearMonthDay>
private var timelineMap = TimelineMap()
private var curItemIndex = 0
private var curRecordsOffset: CGFloat = 0
private var curTimelineOffset: CGFloat = 0
private var timelineStart: Date?
#IBOutlet var recordsCollectionView: UICollectionView!
#IBOutlet var timelineCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
data = fetchData()
timelineStart = Array(data.keys).first?.date
configureRecordsDataSource()
configureTimelineDataSource()
configureTimelineSupplementaryViews()
applyRecordsSnapshot()
applyTimelineSnapshot()
if let collectionViewLayout = timelineCollectionView.collectionViewLayout as? TimelineLayout {
collectionViewLayout.delegate = self
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var i: CGFloat = 0
for _ in data {
let recordOffset = self.recordsCollectionView.frame.width * i
let timelineOffset = (timelineCollectionView.frame.width + CGFloat(10)) * i
timelineMap.set(recordOffset: recordOffset, timelineOffset: timelineOffset)
i += 1
}
}
private func fetchData() -> [YearMonthDay:[TestRecord]] {
var data: [YearMonthDay:[
TestRecord]] = [:]
var testRecords:[TestRecord] = []
for i in 0...6{
let testRecord = TestRecord(daysBack: i*2, progression: 0)
testRecords.append(testRecord)
}
for record in testRecords {
let ymd = YearMonthDay(date:record.timeStamp,records: [])
if var day = data[ymd] {
day.append(record)
} else {
data[ymd] = [record]
}
}
return data
}
extension ProgressViewController {
//RECORDS data
fileprivate func makeRecordsDataSource() -> RecordsDataSource {
let dataSource = RecordsDataSource(
collectionView: recordsCollectionView,
cellProvider: { (collectionView, indexPath, testRecord) ->
UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecordCollectionViewCell.identifier, for: indexPath) as? RecordCollectionViewCell
cell?.configure(with: testRecord)
return cell
})
return dataSource
}
func configureRecordsDataSource() {
self.recordsCollectionView.register(RecordCollectionViewCell.nib, forCellWithReuseIdentifier: RecordCollectionViewCell.identifier)
}
func applyRecordsSnapshot() {
// 2
var snapshot = RecordsDataSourceSnapshot()
for (ymd,records) in data {
snapshot.appendSections([ymd])
snapshot.appendItems(records,toSection: ymd)
}
recordsDataSource.apply(snapshot, animatingDifferences: false)
}
}
extension ProgressViewController {
enum TimelineSection {
case main
}
fileprivate func makeTimelineDataSource() -> TimelineDataSource {
let dataSource = TimelineDataSource(
collectionView: self.timelineCollectionView,
cellProvider: { (collectionView, indexPath, testRecord) ->
UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TimelineDayCell.identifier, for: indexPath) as? TimelineDayCell
cell?.backgroundColor = .orange
cell?.dayLabel.text = String(indexPath.section)+","+String(indexPath.row)
return cell
})
return dataSource
}
func configureTimelineDataSource() {
self.timelineCollectionView!.register(TimelineDayCell.nib, forCellWithReuseIdentifier: TimelineDayCell.identifier)
timelineCollectionView.register(
UINib(nibName: "MonthHeader", bundle: nil),
forSupplementaryViewOfKind: TimelineLayout.Element.month.kind,
withReuseIdentifier: TimelineLayout.Element.month.id)
timelineCollectionView.register(
UINib(nibName: "TimelineDayCell", bundle: nil),
forSupplementaryViewOfKind: TimelineLayout.Element.day.kind,
withReuseIdentifier: TimelineLayout.Element.day.id)
timelineCollectionView.register(
UINib(nibName: "YearLabel", bundle: nil),
forSupplementaryViewOfKind: TimelineLayout.Element.year.kind,
withReuseIdentifier: TimelineLayout.Element.year.id)
}
func applyTimelineSnapshot(animatingDifferences: Bool = false) {
// 2
var snapshot = TimelineDataSourceSnapshot()
snapshot.appendSections([.main])
snapshot.appendItems(Array(data.keys))
timelineDataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
func configureTimelineSupplementaryViews(){
timelineDataSource.supplementaryViewProvider = { (
collectionView: UICollectionView,
kind: String,
indexPath: IndexPath) -> UICollectionReusableView? in
switch kind {
case TimelineLayout.Element.month.kind:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TimelineLayout.Element.month.id, for: indexPath)
if let monthLabelView = supplementaryView as? MonthHeader {
let active = Calendar.current.date(byAdding: .month, value: indexPath.item, to: self.timelineStart!)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM"
let monthString = dateFormatter.string(from: active!)
monthLabelView.monthLabel.text = monthString
}
return supplementaryView
case TimelineLayout.Element.year.kind:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TimelineLayout.Element.year.id, for: indexPath)
if let yearLabelView = supplementaryView as? YearLabel {
let labelDate = Calendar.current.date(byAdding: .year, value: indexPath.item, to: self.timelineStart!)!
let year = Calendar.current.component(.year, from: labelDate)
yearLabelView.yearLabel.text = String(year)
}
return supplementaryView
default:
fatalError("This should never happen!!")
}
}
}
}
extension ProgressViewController: TimelineLayoutDelegate {
func collectionView(_ collectionView: UICollectionView, dateAtIndexPath indexPath: IndexPath) -> Date? {
return timelineDataSource.itemIdentifier(for: indexPath)?.date
}
}
Then in my custom TimelineLayout I have
class TimelineLayout: UICollectionViewLayout {
weak var delegate: TimelineLayoutDelegate!
enum Element: String {
case day
case month
case year
var id: String {
return self.rawValue
}
var kind: String {
return "Kind\(self.rawValue.capitalized)"
}
}
private var oldBounds = CGRect.zero
private var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
private var contentWidth = CGFloat()
private var cache = [Element: [IndexPath: UICollectionViewLayoutAttributes]]()
private var monthHeight: CGFloat = 19
private var yearHeight: CGFloat = 11
private var dayHeight: CGFloat = 30
private var cellWidth: CGFloat = 2.5
private var collectionViewStartY: CGFloat {
guard let collectionView = collectionView else {
return 0
}
return collectionView.bounds.minY
}
private var collectionViewHeight: CGFloat {
return collectionView!.frame.height
}
override public var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: collectionViewHeight)
}
}
extension TimelineLayout {
override public func prepare() {
guard let collectionView = collectionView,
cache.isEmpty else {
return
}
collectionView.decelerationRate = .fast
updateInsets()
cache.removeAll(keepingCapacity: true)
cache[.year] = [IndexPath: UICollectionViewLayoutAttributes]()
cache[.month] = [IndexPath: UICollectionViewLayoutAttributes]()
cache[.day] = [IndexPath: UICollectionViewLayoutAttributes]()
oldBounds = collectionView.bounds
var timelineStart: Date?
var timelineEnd: Date?
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
let cellIndexPath = IndexPath(item: item, section: 0)
guard let cellDate = delegate.collectionView(collectionView, dateAtIndexPath: cellIndexPath) else {
return
}
if item == 0 {
let firstDate = cellDate
timelineStart = Calendar.current.startOfDay(for: firstDate)
}
if item == collectionView.numberOfItems(inSection: 0) - 1 {
timelineEnd = cellDate
}
let startX = CGFloat(cellDate.days(from: timelineStart!)) * cellWidth
let dayCellattributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
dayCellattributes.frame = CGRect(x: startX, y: collectionViewStartY + yearHeight + monthHeight, width: cellWidth, height: dayHeight)
cache[.day]?[cellIndexPath] = dayCellattributes
contentWidth = max(startX + cellWidth,contentWidth)
}
///TODO - what if there are no items in the section....
guard let monthStart = timelineStart?.startOfMonth(), let monthEnd = timelineEnd?.endOfMonth() else { return }
let begin = Calendar.current.date(byAdding: .month, value: -4, to: monthStart)
let end = Calendar.current.date(byAdding: .month, value: 4, to: monthEnd)
var date: Date = begin!
let initalOffset = CGFloat((timelineStart?.days(from: date))!) * cellWidth
var monthOffset: CGFloat = 0
var monthIndex: Int = 0
var yearIndex: Int = 0
while date <= end! {
let daysInMonth = Calendar.current.range(of: .day, in: .month, for: date)?.count
let monthWidth = cellWidth * CGFloat(daysInMonth!)
//let monthIndex = date.months(from: timelineStart!)
let monthLabelIndexPath = IndexPath(item: monthIndex, section: 0)
let monthLabelAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: Element.month.kind, with: monthLabelIndexPath)
let startX = monthOffset - initalOffset
monthLabelAttributes.frame = CGRect(x: startX, y: collectionViewStartY + yearHeight, width: monthWidth, height: monthHeight)
print(startX,"spaghet")
cache[.month]?[monthLabelIndexPath] = monthLabelAttributes
monthOffset += monthWidth
if Calendar.current.component(.month, from: date) == 1 || yearIndex == 0 {
//draw year
//let year = Calendar.current.component(.year, from: date)
//let yearIndex = date.years(from: timelineStart!)
let daysFromStartOfYear = date.days(from: date.startOfYear())
let startYearX = startX - CGFloat(daysFromStartOfYear) * cellWidth
let yearLabelIndexPath = IndexPath(item: yearIndex, section: 0)
let yearLabelAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: Element.year.kind, with: yearLabelIndexPath)
yearLabelAttributes.frame = CGRect(x: startYearX, y: collectionViewStartY, width: CGFloat(30), height: yearHeight)
cache[.year]?[yearLabelIndexPath] = yearLabelAttributes
yearIndex += 1
}
date = Calendar.current.date(byAdding: .month, value: 1, to: date)!
monthIndex += 1
}
}
}
extension TimelineLayout {
public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
switch elementKind {
case Element.year.kind:
return cache[.year]?[indexPath]
default:
return cache[.month]?[indexPath]
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
visibleLayoutAttributes.removeAll(keepingCapacity: true)
if let yearAttrs = cache[.year], let monthAttrs = cache[.month], let dayAttrs = cache[.day] {
for (_, attributes) in yearAttrs {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
for (_, attributes) in monthAttrs {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
for (_, attributes) in dayAttrs {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
// visibleLayoutAttributes.append(self.shiftedAttributes(from: attributes))
}
}
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = cache[.day]?[indexPath] else { fatalError("No attributes cached") }
return attributes
// return shiftedAttributes(from: attributes)
}
override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if oldBounds.size != newBounds.size {
cache.removeAll(keepingCapacity: true)
}
return true
}
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
if context.invalidateDataSourceCounts { cache.removeAll(keepingCapacity: true) }
super.invalidateLayout(with: context)
}
}
As you can see I do create timelineDataSource supplementary View Provider in the view controller. Then in the Timeline Layout I implement layoutAttributesForElements(in rect: CGRect) and layoutAttributesForSupplementaryView(ofKind .... The latter never gets called - the error comes first. layoutAttributesForElements(in rect: CGRect) does get called and filled with dayAttrs, monthAttrs, and yearAttrs. However still, the error occurs afterwards:
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: self.supplementaryViewProvider || (self.supplementaryReuseIdentifierProvider && self.supplementaryViewConfigurationHandler)'
terminating with uncaught exception of type NSException
What am I missing?
Edit: I want to add the detail that when I put a breakpoint in that supplementaryViewProvider it's never called. So maybe I am doing something wrong in layoutAttributesForElements(in Rect?)
I want to use CalendarKit in my project github here
It's written using UIKit, but my project uses SwiftUI. Can I use CustomCalendarExampleController in SwiftUI? (maybe via UIViewControllerRepresentable or smth else?)
CustomCalendarExampleController -
class CustomCalendarExampleController: DayViewController, DatePickerControllerDelegate {
var data = [["Breakfast at Tiffany's",
"New York, 5th avenue"],
["Workout",
"Tufteparken"],
["Meeting with Alex",
"Home",
"Oslo, Tjuvholmen"],
["Beach Volleyball",
"Ipanema Beach",
"Rio De Janeiro"],
["WWDC",
"Moscone West Convention Center",
"747 Howard St"],
["Google I/O",
"Shoreline Amphitheatre",
"One Amphitheatre Parkway"],
["✈️️ to Svalbard ❄️️❄️️❄️️❤️️",
"Oslo Gardermoen"],
["💻📲 Developing CalendarKit",
"🌍 Worldwide"],
["Software Development Lecture",
"Mikpoli MB310",
"Craig Federighi"],
]
var generatedEvents = [EventDescriptor]()
var alreadyGeneratedSet = Set<Date>()
var colors = [UIColor.blue,
UIColor.yellow,
UIColor.green,
UIColor.red]
lazy var customCalendar: Calendar = {
let customNSCalendar = NSCalendar(identifier: NSCalendar.Identifier.gregorian)!
customNSCalendar.timeZone = TimeZone(abbreviation: "CEST")!
let calendar = customNSCalendar as Calendar
return calendar
}()
override func loadView() {
calendar = customCalendar
dayView = DayView(calendar: calendar)
view = dayView
}
override func viewDidLoad() {
super.viewDidLoad()
title = "CalendarKit Demo"
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Change Date",
style: .plain,
target: self,
action: #selector(presentDatePicker))
navigationController?.navigationBar.isTranslucent = false
dayView.autoScrollToFirstEvent = true
reloadData()
}
#objc func presentDatePicker() {
let picker = DatePickerController()
// let calendar = dayView.calendar
// picker.calendar = calendar
// picker.date = dayView.state!.selectedDate
picker.datePicker.timeZone = TimeZone(secondsFromGMT: 0)!
picker.delegate = self
let navC = UINavigationController(rootViewController: picker)
navigationController?.present(navC, animated: true, completion: nil)
}
func datePicker(controller: DatePickerController, didSelect date: Date?) {
if let date = date {
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(secondsFromGMT: 0)!
let offsetDate = dateOnly(date: date, calendar: dayView.calendar)
print(offsetDate)
dayView.state?.move(to: offsetDate)
}
controller.dismiss(animated: true, completion: nil)
}
func dateOnly(date: Date, calendar: Calendar) -> Date {
let yearComponent = calendar.component(.year, from: date)
let monthComponent = calendar.component(.month, from: date)
let dayComponent = calendar.component(.day, from: date)
let zone = calendar.timeZone
let newComponents = DateComponents(timeZone: zone,
year: yearComponent,
month: monthComponent,
day: dayComponent)
let returnValue = calendar.date(from: newComponents)
// let returnValue = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: date)
return returnValue!
}
// MARK: EventDataSource
override func eventsForDate(_ date: Date) -> [EventDescriptor] {
if !alreadyGeneratedSet.contains(date) {
alreadyGeneratedSet.insert(date)
generatedEvents.append(contentsOf: generateEventsForDate(date))
}
return generatedEvents
}
private func generateEventsForDate(_ date: Date) -> [EventDescriptor] {
var workingDate = date.add(TimeChunk.dateComponents(hours: Int(arc4random_uniform(10) + 5)))
var events = [Event]()
for i in 0...4 {
let event = Event()
let duration = Int(arc4random_uniform(160) + 60)
let datePeriod = TimePeriod(beginning: workingDate,
chunk: TimeChunk.dateComponents(minutes: duration))
event.startDate = datePeriod.beginning!
event.endDate = datePeriod.end!
var info = data[Int(arc4random_uniform(UInt32(data.count)))]
let timezone = dayView.calendar.timeZone
print(timezone)
info.append(datePeriod.beginning!.format(with: "dd.MM.YYYY", timeZone: timezone))
info.append("\(datePeriod.beginning!.format(with: "HH:mm", timeZone: timezone)) - \(datePeriod.end!.format(with: "HH:mm", timeZone: timezone))")
event.text = info.reduce("", {$0 + $1 + "\n"})
event.color = colors[Int(arc4random_uniform(UInt32(colors.count)))]
event.isAllDay = Int(arc4random_uniform(2)) % 2 == 0
// Event styles are updated independently from CalendarStyle
// hence the need to specify exact colors in case of Dark style
if #available(iOS 12.0, *) {
if traitCollection.userInterfaceStyle == .dark {
event.textColor = textColorForEventInDarkTheme(baseColor: event.color)
event.backgroundColor = event.color.withAlphaComponent(0.6)
}
}
events.append(event)
let nextOffset = Int(arc4random_uniform(250) + 40)
workingDate = workingDate.add(TimeChunk.dateComponents(minutes: nextOffset))
event.userInfo = String(i)
}
print("Events for \(date)")
return events
}
private func textColorForEventInDarkTheme(baseColor: UIColor) -> UIColor {
var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
baseColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
return UIColor(hue: h, saturation: s * 0.3, brightness: b, alpha: a)
}
// MARK: DayViewDelegate
private var createdEvent: EventDescriptor?
override func dayViewDidSelectEventView(_ eventView: EventView) {
guard let descriptor = eventView.descriptor as? Event else {
return
}
print("Event has been selected: \(descriptor) \(String(describing: descriptor.userInfo))")
}
override func dayViewDidLongPressEventView(_ eventView: EventView) {
guard let descriptor = eventView.descriptor as? Event else {
return
}
endEventEditing()
print("Event has been longPressed: \(descriptor) \(String(describing: descriptor.userInfo))")
beginEditing(event: descriptor, animated: true)
print(Date())
}
override func dayView(dayView: DayView, didTapTimelineAt date: Date) {
endEventEditing()
print("Did Tap at date: \(date)")
}
override func dayViewDidBeginDragging(dayView: DayView) {
print("DayView did begin dragging")
}
override func dayView(dayView: DayView, willMoveTo date: Date) {
print("DayView = \(dayView) will move to: \(date)")
}
override func dayView(dayView: DayView, didMoveTo date: Date) {
print("DayView = \(dayView) did move to: \(date)")
}
override func dayView(dayView: DayView, didLongPressTimelineAt date: Date) {
print("Did long press timeline at date \(date)")
// Cancel editing current event and start creating a new one
endEventEditing()
let event = generateEventNearDate(date)
print("Creating a new event")
create(event: event, animated: true)
createdEvent = event
}
private func generateEventNearDate(_ date: Date) -> EventDescriptor {
let duration = Int(arc4random_uniform(160) + 60)
let startDate = date.subtract(TimeChunk.dateComponents(minutes: Int(CGFloat(duration) / 2)))
let event = Event()
let datePeriod = TimePeriod(beginning: startDate,
chunk: TimeChunk.dateComponents(minutes: duration))
event.startDate = datePeriod.beginning!
event.endDate = datePeriod.end!
var info = data[Int(arc4random_uniform(UInt32(data.count)))]
let timezone = dayView.calendar.timeZone
info.append(datePeriod.beginning!.format(with: "dd.MM.YYYY", timeZone: timezone))
info.append("\(datePeriod.beginning!.format(with: "HH:mm", timeZone: timezone)) - \(datePeriod.end!.format(with: "HH:mm", timeZone: timezone))")
event.text = info.reduce("", {$0 + $1 + "\n"})
event.color = colors[Int(arc4random_uniform(UInt32(colors.count)))]
event.editedEvent = event
// Event styles are updated independently from CalendarStyle
// hence the need to specify exact colors in case of Dark style
if #available(iOS 12.0, *) {
if traitCollection.userInterfaceStyle == .dark {
event.textColor = textColorForEventInDarkTheme(baseColor: event.color)
event.backgroundColor = event.color.withAlphaComponent(0.6)
}
}
return event
}
override func dayView(dayView: DayView, didUpdate event: EventDescriptor) {
print("did finish editing \(event)")
print("new startDate: \(event.startDate) new endDate: \(event.endDate)")
if let _ = event.editedEvent {
event.commitEditing()
}
if let createdEvent = createdEvent {
createdEvent.editedEvent = nil
generatedEvents.append(createdEvent)
self.createdEvent = nil
endEventEditing()
}
reloadData()
}
}
DayViewController -
#if os(iOS)
import UIKit
import DateToolsSwift
open class DayViewController: UIViewController, EventDataSource, DayViewDelegate {
public lazy var dayView: DayView = DayView()
public var dataSource: EventDataSource? {
get {
return dayView.dataSource
}
set(value) {
dayView.dataSource = value
}
}
public var delegate: DayViewDelegate? {
get {
return dayView.delegate
}
set(value) {
dayView.delegate = value
}
}
public var calendar = Calendar.autoupdatingCurrent {
didSet {
dayView.calendar = calendar
}
}
open override func loadView() {
view = dayView
}
override open func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = []
view.tintColor = SystemColors.systemRed
dataSource = self
delegate = self
dayView.reloadData()
let sizeClass = traitCollection.horizontalSizeClass
configureDayViewLayoutForHorizontalSizeClass(sizeClass)
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
dayView.scrollToFirstEventIfNeeded()
}
open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
configureDayViewLayoutForHorizontalSizeClass(newCollection.horizontalSizeClass)
}
open func configureDayViewLayoutForHorizontalSizeClass(_ sizeClass: UIUserInterfaceSizeClass) {
dayView.transitionToHorizontalSizeClass(sizeClass)
}
// MARK: - CalendarKit API
open func move(to date: Date) {
dayView.move(to: date)
}
open func reloadData() {
dayView.reloadData()
}
open func updateStyle(_ newStyle: CalendarStyle) {
dayView.updateStyle(newStyle)
}
open func eventsForDate(_ date: Date) -> [EventDescriptor] {
return [Event]()
}
// MARK: - DayViewDelegate
open func dayViewDidSelectEventView(_ eventView: EventView) {
}
open func dayViewDidLongPressEventView(_ eventView: EventView) {
}
open func dayView(dayView: DayView, didTapTimelineAt date: Date) {
}
open func dayViewDidBeginDragging(dayView: DayView) {
}
open func dayView(dayView: DayView, willMoveTo date: Date) {
}
open func dayView(dayView: DayView, didMoveTo date: Date) {
}
open func dayView(dayView: DayView, didLongPressTimelineAt date: Date) {
}
open func dayView(dayView: DayView, didUpdate event: EventDescriptor) {
}
// MARK: - Editing
open func create(event: EventDescriptor, animated: Bool = false) {
dayView.create(event: event, animated: animated)
}
open func beginEditing(event: EventDescriptor, animated: Bool = false) {
dayView.beginEditing(event: event, animated: animated)
}
open func endEventEditing() {
dayView.endEventEditing()
}
}
#endif
There is nothing interesting in DatePickerControllerDelegate.
Thanks for the replies!
Lol I don't know why it didn't work before, but if you are looking for something like this
struct CustomController: UIViewControllerRepresentable {
func updateUIViewController(_ uiViewController: UIViewController, context: Context){
}
func makeUIViewController(context: Context) -> UIViewController {
let dayViewController = CustomCalendarExampleController()
return dayViewController
}
}
How can I check that I clicked on today date?
https://github.com/WenchaoIOS/FSCalendar
I got the below output:
todaydateString = 2020-05-18
dateString = 2020-05-17
which is not matching:
import UIKit
class LoadViewExampleViewController: UIViewController, FSCalendarDataSource, FSCalendarDelegate {
private weak var calendar: FSCalendar!
fileprivate lazy var dateFormatter1: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
let dateString = "2020-05-15"
let dateString1 = "2020-05-16"
let dateString2 = "2020-05-13"
var selectedDates = NSMutableArray()
override func loadView() {
selectedDates = [dateString, dateString1, dateString2]
let view = UIView(frame: UIScreen.main.bounds)
view.backgroundColor = UIColor.groupTableViewBackground
self.view = view
let height: CGFloat = UIDevice.current.model.hasPrefix("iPad") ? 400 : 300
let calendar = FSCalendar(frame: CGRect(x: 0, y: self.navigationController!.navigationBar.frame.maxY, width: self.view.bounds.width, height: height))
calendar.dataSource = self
calendar.delegate = self
calendar.backgroundColor = UIColor.clear
calendar.appearance.todayColor = UIColor.gray
// calendar.appearance.borderDefaultColor = UIColor.black
//calendar.appearance.borderRadius = 0
self.view.addSubview(calendar)
self.calendar = calendar
}
func calendar(_ calendar: FSCalendar, didDeselect date: Date, at monthPosition: FSCalendarMonthPosition) {
let todaydate = Date()
let dateString = self.dateFormatter1.string(from: date)
let todaydateString = self.dateFormatter1.string(from: todaydate)
print("todaydateString = \(todaydateString)")
print("dateString = \(dateString)")
if dateString == todaydateString
{
print("GOOD")
}
}
func calendar(_ calendar: FSCalendar, imageFor date: Date) -> UIImage? {
let dateString = self.dateFormatter1.string(from: date)
if self.selectedDates.contains(dateString) {
let image : UIImage = UIImage(named: "checkbox_Tick")!
return image
}
return UIImage()
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "FSCalendar"
}
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
if monthPosition == .previous || monthPosition == .next {
calendar.setCurrentPage(date, animated: true)
}
}
}
I have been working on this project which needs several uiimageviews which, they are in a uicollectionviewcell, to be updated based on the information on the database!
i have tried background threads but it didnt help!im wondering why the images wont update!
here is my code
func update_cells()
{
if sqlite3_prepare(self.db, "SELECT * FROM `habit_days` WHERE `habit_id` = \(self.id!)", -1, &self.stmt, nil) == SQLITE_OK
{
while(sqlite3_step(self.stmt) == SQLITE_ROW)
{
if(String(cString: sqlite3_column_text(self.stmt, 3 )) == "checked")
{
DispatchQueue.main.async {
let image = self.view.viewWithTag(self.day_id) as? UIImageView
image?.image = UIImage(named: "mark")
self.day_id = self.day_id + 1
}
}else{
DispatchQueue.main.async {
let image = self.view.viewWithTag(self.day_id) as? UIImageView
image?.image = UIImage(named: "uncheck")
self.day_id = self.day_id + 1
}
}
self.date = String(cString:sqlite3_column_text(self.stmt, 2))
let frmt = DateFormatter()
frmt.dateFormat = "yyyy/MM/dd HH:mm:ss"
self.lastdate = frmt.date(from: self.date!)
}
self.todayedate = Date()
}
}
any help would be appriciated
///////////////////
UPDATE
So i have changed my code to this and now i have my code working but still 1 problem :| all the cells are changing except the first one!
//
// Habit_Activity.swift
// Habit
//
// Created by shayan rahimian on 3/3/18.
// Copyright © 2018 shayan rahimian. All rights reserved.
//
import UIKit
import SQLite3
class Habit_check: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return days
}
#IBOutlet weak var des: UITextView!
#IBOutlet weak var col: UICollectionView!
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.layer.borderWidth = 1.5
cell.layer.borderColor = self.hexStringToUIColor(hex: "#a0a0a0").cgColor
let image = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
print(indexPath.row)
image.tag = indexPath.row
cell.contentView.addSubview(image)
return cell
}
#IBAction func back(_ sender: Any) {
let back = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "habits_list") as? Habits_list
self.present(back!, animated: true, completion: nil)
}
#IBAction func check(_ sender: Any) {
let frmt = DateFormatter()
frmt.dateFormat = "yyyy/MM/dd HH:mm:ss"
let mystr = frmt.string(from: todayedate)
print(mystr)
if ( days_passed > 0){
sqlite3_exec(db, "INSERT INTO `habit_days`(`id`,`habit_id`,`time`,`state`) VALUES(null,\(self.id!),'\(mystr)','checked')", nil, nil, nil)
let image = self.view.viewWithTag(day_id) as? UIImageView
image?.image = UIImage(named: "mark")
days_passed = 0
day_id = day_id + 1
}
else{
self.view.makeToast("باید از اخرین تغیریرات شما ۲۴ ساعت گذشته باشد", duration: 2.0, point: CGPoint(x: 110.0, y: 110.0), title: "", image: nil, style: ToastStyle.init(), completion: nil)
}
}
#IBOutlet weak var cell_view: UIView!
#IBAction func del(_ sender: Any) {
let frmt = DateFormatter()
frmt.dateFormat = "yyyy/MM/dd HH:mm:ss"
let mystr = frmt.string(from: todayedate)
if ( days_passed > 0){
sqlite3_exec(db, "INSERT INTO `habit_days`(`id`,`habit_id`,`time`,`state`) VALUES(null,\(self.id!),'\(mystr)','unchecked')", nil, nil, nil)
let image = self.view.viewWithTag(self.day_id) as? UIImageView
image?.image = UIImage(named: "uncheck")
days_passed = 0
day_id = day_id + 1
}
else{
self.view.makeToast("باید از اخرین تغیریرات شما ۲۴ ساعت گذشته باشد", duration: 2.0, point: CGPoint(x: 110.0, y: 110.0), title: "", image: nil, style: ToastStyle.init(), completion: nil)
}
}
#IBOutlet weak var name_label: UILabel!
var id : Int!
var db: OpaquePointer?
var stmt:OpaquePointer?
var name: String!
var days: Int!
var day_id = 0
var date: String!
var lastdate: Date!
var todayedate: Date!
var days_passed = 999
weak var delegate: Habits_list!
override func viewDidLoad() {
super.viewDidLoad()
cell_view.layer.borderWidth = 3
cell_view.layer.borderColor = self.hexStringToUIColor(hex: "#a0a0a0").cgColor
cell_view.layer.cornerRadius = 10
col.layer.borderWidth = 3
col.layer.borderColor = self.hexStringToUIColor(hex: "#a0a0a0").cgColor
col.layer.cornerRadius = 10
des.layer.borderWidth = 3
des.layer.borderColor = self.hexStringToUIColor(hex: "#a0a0a0").cgColor
des.layer.cornerRadius = 10
get_info()
DispatchQueue.global(qos: .background).async {
self.update_cells()
if (self.lastdate != nil) {
self.days_passed = Calendar.current.dateComponents([.day], from: self.lastdate, to: self.todayedate).day!
}
}
}
func update_cells()
{
if sqlite3_prepare(self.db, "SELECT * FROM `habit_days` WHERE `habit_id` = \(self.id!)", -1, &self.stmt, nil) == SQLITE_OK
{
while(sqlite3_step(self.stmt) == SQLITE_ROW)
{
if(String(cString: sqlite3_column_text(self.stmt, 3 )) == "checked")
{
DispatchQueue.main.async {
let image = self.view.viewWithTag(self.day_id) as? UIImageView
image?.image = UIImage(named: "mark")
self.day_id = self.day_id + 1
}
}else{
DispatchQueue.main.async {
let image = self.view.viewWithTag(self.day_id) as? UIImageView
image?.image = UIImage(named: "uncheck")
self.day_id = self.day_id + 1
}
}
self.date = String(cString:sqlite3_column_text(self.stmt, 2))
let frmt = DateFormatter()
frmt.dateFormat = "yyyy/MM/dd HH:mm:ss"
self.lastdate = frmt.date(from: self.date!)
}
self.todayedate = Date()
}
}
func get_info()
{
let fileNameToDelete = "habits.sqlite"
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0
{
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
print("Local path = \(filePath)")
} else
{
print("Could not find local directory to store file")
return
}
let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("user.sqlite")
if sqlite3_open(fileURL.path, &db) == SQLITE_OK
{
if sqlite3_prepare(db, "SELECT * FROM `habits` WHERE `id` = \(self.id!)", -1, &stmt, nil) == SQLITE_OK
{
while(sqlite3_step(stmt) == SQLITE_ROW){
self.name = String(cString: sqlite3_column_text(stmt, 1))
name_label.text = self.name
self.days = Int(sqlite3_column_int(stmt, 2))
}
}
}
}
}
cell with tag number 0 is not changing! but other ones are ok.
///////////////////////
UPDATE 3 (Some how solved)
i have updated my code as below, it is not what i wanted but it is a work around in some way
//
// Habit_Activity.swift
// Habit
//
// Created by shayan rahimian on 3/3/18.
// Copyright © 2018 shayan rahimian. All rights reserved.
//
import UIKit
import SQLite3
class Habit_check: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return days + 1
}
#IBOutlet weak var des: UITextView!
#IBOutlet weak var col: UICollectionView!
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.transform = CGAffineTransform(scaleX: -1, y: 1)
if indexPath.row != 0 {
cell.layer.borderWidth = 1.5
cell.layer.borderColor = self.hexStringToUIColor(hex: "#a0a0a0").cgColor
let image = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
image.tag = indexPath.row
cell.contentView.addSubview(image)
}
return cell
}
#IBAction func back(_ sender: Any) {
let back = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "habits_list") as? Habits_list
self.present(back!, animated: true, completion: nil)
}
#IBAction func check(_ sender: Any) {
let frmt = DateFormatter()
frmt.dateFormat = "yyyy/MM/dd HH:mm:ss"
let mystr = frmt.string(from: todayedate)
print(mystr)
if (day_id <= days) {
if ( days_passed > 0){
sqlite3_exec(db, "INSERT INTO `habit_days`(`id`,`habit_id`,`time`,`state`) VALUES(null,\(self.id!),'\(mystr)','checked')", nil, nil, nil)
let image = self.view.viewWithTag(day_id) as? UIImageView
image?.image = UIImage(named: "mark")
days_passed = 0
day_id = day_id + 1
}
else{
self.view.makeToast("باید از اخرین تغییرات شما ۲۴ ساعت گذشته باشد", duration: 2.0, point: CGPoint(x: 110.0, y: 110.0), title: "", image: nil, style: ToastStyle.init(), completion: nil)
}
}else{
self.view.makeToast("تعداد روز های عادت شما به پایان رسیده", duration: 2.0, point: CGPoint(x: 110.0, y: 110.0), title: "", image: nil, style: ToastStyle.init(), completion: nil)
}
}
#IBOutlet weak var cell_view: UIView!
#IBAction func del(_ sender: Any) {
let frmt = DateFormatter()
frmt.dateFormat = "yyyy/MM/dd HH:mm:ss"
let mystr = frmt.string(from: todayedate)
if (day_id <= days) {
if ( days_passed > 0){
sqlite3_exec(db, "INSERT INTO `habit_days`(`id`,`habit_id`,`time`,`state`) VALUES(null,\(self.id!),'\(mystr)','unchecked')", nil, nil, nil)
let image = self.view.viewWithTag(self.day_id) as? UIImageView
image?.image = UIImage(named: "uncheck")
days_passed = 0
day_id = day_id + 1
}
else{
self.view.makeToast("باید از اخرین تغیریرات شما ۲۴ ساعت گذشته باشد", duration: 2.0, point: CGPoint(x: 110.0, y: 110.0), title: "", image: nil, style: ToastStyle.init(), completion: nil)
}
}else{
self.view.makeToast("تعداد روز های عادت شما به پایان رسیده", duration: 2.0, point: CGPoint(x: 110.0, y: 110.0), title: "", image: nil, style: ToastStyle.init(), completion: nil)
}
}
#IBOutlet weak var name_label: UILabel!
var id : Int!
var db: OpaquePointer?
var stmt:OpaquePointer?
var name: String!
var days: Int!
var day_id = 1
var date: String!
var lastdate: Date!
var todayedate: Date!
var days_passed = 999
weak var delegate: Habits_list!
override func viewDidLoad() {
super.viewDidLoad()
get_info()
des.textAlignment = .right
des.makeTextWritingDirectionRightToLeft(self)
col.transform = CGAffineTransform(scaleX: -1, y: 1)
}
override func viewDidAppear(_ animated: Bool) {
cell_view.layer.borderWidth = 3
cell_view.layer.borderColor = self.hexStringToUIColor(hex: "#a0a0a0").cgColor
cell_view.layer.cornerRadius = 10
col.layer.borderWidth = 3
col.layer.borderColor = self.hexStringToUIColor(hex: "#a0a0a0").cgColor
col.layer.cornerRadius = 10
des.layer.borderWidth = 3
des.layer.borderColor = self.hexStringToUIColor(hex: "#a0a0a0").cgColor
des.layer.cornerRadius = 10
DispatchQueue.global(qos: .background).async {
self.update_cells()
if (self.lastdate != nil) {
self.days_passed = Calendar.current.dateComponents([.day], from: self.lastdate, to: self.todayedate).day!
}
}
}
func update_cells()
{
if sqlite3_prepare(self.db, "SELECT * FROM `habit_days` WHERE `habit_id` = \(self.id!)", -1, &self.stmt, nil) == SQLITE_OK
{
while(sqlite3_step(self.stmt) == SQLITE_ROW)
{
if(String(cString: sqlite3_column_text(self.stmt, 3 )) == "checked")
{
DispatchQueue.main.async {
let image = self.view.viewWithTag(self.day_id) as? UIImageView
image?.image = UIImage(named: "mark")
self.day_id = self.day_id + 1
}
}else{
DispatchQueue.main.async {
let image = self.view.viewWithTag(self.day_id) as? UIImageView
image?.image = UIImage(named: "uncheck")
self.day_id = self.day_id + 1
}
}
self.date = String(cString:sqlite3_column_text(self.stmt, 2))
let frmt = DateFormatter()
frmt.dateFormat = "yyyy/MM/dd HH:mm:ss"
self.lastdate = frmt.date(from: self.date!)
}
self.todayedate = Date()
}
}
func get_info()
{
let fileNameToDelete = "habits.sqlite"
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0
{
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
print("Local path = \(filePath)")
} else
{
print("Could not find local directory to store file")
return
}
let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("user.sqlite")
if sqlite3_open(fileURL.path, &db) == SQLITE_OK
{
if sqlite3_prepare(db, "SELECT * FROM `habits` WHERE `id` = \(self.id!)", -1, &stmt, nil) == SQLITE_OK
{
while(sqlite3_step(stmt) == SQLITE_ROW){
self.name = String(cString: sqlite3_column_text(stmt, 1))
name_label.text = self.name
self.des.text = String(cString: sqlite3_column_text(stmt, 3))
self.days = Int(sqlite3_column_int(stmt, 2))
}
}
}
}
}