Swift 3: Internal Extension to Set Time - swift3

I had this internal Date extension that appears to no longer work in Swift 3:
internal extension DateComponents {
func to12pm() {
self.hour = 12
self.minute = 0
self.second = 0
}
}
The error message is:
Cannot assign to property: 'self' is immutable
How do I achieve this in Swift 3?
Additional Info (if needed): It was called by this Date extension:
func endOfWeek(_ weekday: Int) -> Date? {
guard
let cal = Calendar.current,
var comp: DateComponents = (cal as Calendar).components([.weekOfYear], from: self)
else {
return nil
}
comp.weekOfYear = 1
comp.day -= 1
comp.to12pm()
return (cal as NSCalendar).date(byAdding: comp, to: self.startOfWeek(weekday)!, options: [])!
}
Which now looks like this in Swift 3:
func endOfWeek(_ weekday: Int) -> Date? {
let cal = Calendar.current
var comp = cal.dateComponents([.weekOfYear], from: self)
comp.weekOfYear = 1
comp.day? -= 1
//This does not have the "comp.to12pm()" that the Swift 2 version did
return cal.date(byAdding: comp, to: self.startOfWeek(weekday)!)!
}

DateComponents is now a struct thats why it is throwing that error:
Cannot assign to property: 'self' is immutable
You need to create a new var from self, change it and return it as follow:
extension DateComponents {
var to12pm: DateComponents {
var components = self
components.hour = 12
components.minute = 0
components.second = 0
components.nanosecond = 0
return components
}
}

Related

#Published Array is not updating

i'm currently struggling to fetch any changes from an published variable in SwiftUI. Most of the code is created after this tutorial on YouTube.
It's basically an app, that fetches cryptos from a firebase database. To avoid high server costs I want to update any changes of the coins to the database but not have an observer to lower the download rate.
What's the bug?
When I'm adding a coin to my favorites, it sends the data correctly to the database and updates the UI. However when I try to filter the coins the Coin-array switches back to it's previous state. I also added a breakpoint on the CoinCellViewModel(coin: coin)-Line but it only gets executed when I change the filterBy. Here's a little visualisation of the bug:
Repository
class CoinsRepository: ObservableObject {
#Published var coins = [Coin]()
var ref: DatabaseReference!
init() {
self.ref = Database.database().reference()
loadDatabase(ref)
}
func loadDatabase(_ ref: DatabaseReference) {
ref.child("coins").observeSingleEvent(of: .value) { snapshot in
guard let dictionaries = snapshot.value as? [String: Any] else { return }
var coinNames: [String] = []
self.coins = dictionaries.compactMap({ (key: String, value: Any) in
guard let dic = value as? [String: Any] else { return nil }
coinNames.append(dic["name"] as? String ?? "")
return Coin(dic)
})
}
}
func updateFavorite(_ coin: Coin, state: Bool) {
let path = ref.child("coins/\(coin.name)")
var flag = false
path.updateChildValues(["favorite": state]) { err, ref in
if let err = err {
print("ERROR: \(err.localizedDescription)")
} else {
var i = 0
var newCoinArray = self.coins
for coinA in newCoinArray {
if coinA.name == coin.name {
newCoinArray[i].favorite = state
}
i += 1
}
// I guess here's the error
DispatchQueue.main.async {
self.objectWillChange.send()
self.coins = newCoinArray
}
}
}
}
}
ViewModel
class CoinListViewModel: ObservableObject {
#Published var coinRepository = CoinsRepository()
#Published var coinCellViewModels = [CoinCellViewModel]()
#Published var filterBy: [Bool] = UserDefaults.standard.array(forKey: "filter") as? [Bool] ?? [false, false, false]
#Published var fbPrice: Double = 0.00
#Published var searchText: String = ""
private var cancellables = Set<AnyCancellable>()
init() {
$searchText
.combineLatest(coinRepository.$coins, $fbPrice, $filterBy)
.map(filter)
.sink { coins in
self.coinCellViewModels = coins.map { coin in
CoinCellViewModel(coin: coin)
}
}
.store(in: &cancellables)
}
...
}
updateFavorite(_ coin: Coin, state: Bool) get's called in the CoinCellViewModel() but I guess the code isn't necessary here...
I'm fairly new to the Combine topic and not quite getting all the new methods, so any help is appreciated!

Set local notification for 1 day before or 2 day before swift 3

I want to add local notification in my app. I am filling information in textfields and set date, time and reminder before particular date selected for the exam. Anyone implement such a demo then please suggest me what to do.
Answer is based on what ever i understood, Please change the time and reminder string as per your requirement.
func scheduleNotification(InputUser:String) {
let now: NSDateComponents = NSCalendar.currentCalendar().components([.Hour, .Minute], fromDate: NSDate())
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let date = cal.dateBySettingHour(now.hour, minute: now.minute + 1, second: 0, ofDate: NSDate(), options: NSCalendarOptions())
let reminder = UILocalNotification()
reminder.fireDate = date
reminder.alertBody = InputUser
reminder.alertAction = "Cool"
reminder.soundName = "sound.aif"
reminder.repeatInterval = NSCalendarUnit.Minute
UIApplication.sharedApplication().scheduleLocalNotification(reminder)
print("Firing at \(now.hour):\(now.minute+1)")
}
Set up Daily basic Local Notification 1 Day before or you can be modified it with the help of specific date in Swift 3.1
import UIKit
import UserNotifications
fileprivate struct AlarmKey{
static let startWorkIdentifier = "com.Reminder.Notification" //Add your own Identifier for Local Notification
static let startWork = "Ready for work? Toggle To \"Available\"."
}
class AlarmManager: NSObject{
static let sharedInstance = AlarmManager()
override init() {
super.init()
}
//MARK: - Clear All Previous Notifications
func clearAllNotifications(){
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
} else {
UIApplication.shared.cancelAllLocalNotifications()
}
}
func addReminder(with _ hour: Int, minutes: Int){
clearAllNotifications()
var dateComponent = DateComponents()
dateComponent.hour = hour // example - 7 Change you time here Progrmatically
dateComponent.minute = minutes // example - 00 Change you time here Progrmatically
if #available(iOS 10.0, *) {
dateComponent.timeZone = TimeZone.autoupdatingCurrent
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponent, repeats: false) //Set here **Repeat** condition
let content = UNMutableNotificationContent()
content.body = AlarmKey.startWork //Message Body
content.sound = UNNotificationSound.default()
let notification = UNNotificationRequest(identifier: AlarmKey.startWorkIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(notification) {(error) in
if let error = error {
print("Uh oh! We had an error: \(error)")
}
}
} else {
//iOS *9.4 below if fails the Above Condition....
dateComponent.timeZone = NSTimeZone.system
let calender = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)!
let date = calender.date(from: dateComponent)!
let localNotification = UILocalNotification()
localNotification.fireDate = date
localNotification.alertBody = AlarmKey.startWork
localNotification.repeatInterval = NSCalendar.Unit.day
localNotification.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared.scheduleLocalNotification(localNotification)
}
}
}
//This is optional method if you want to show your Notification foreground condition
extension AlarmManager: UNUserNotificationCenterDelegate{
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Swift.Void){
completionHandler([.alert,.sound])
}
}

UICollectionView reloadData() crashes after emtying datasource dictionary

I have a UICollectionView which takes the results of an API search. The search is triggered by the following code. The results are appended to a dictionary [[String: Any]] and I call self.collectionView.reloadData() after my query completes.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var newValue = textField.text!
let location = min(range.location, newValue.characters.count)
let startIndex = newValue.characters.index(newValue.startIndex, offsetBy: location)
let endIndex = newValue.characters.index(newValue.startIndex, offsetBy: location + range.length)
let newRangeValue = Range<String.Index>(startIndex ..< endIndex)
newValue.replaceSubrange(newRangeValue, with: string)
searchView.searchFieldValueChanged(newValue)
return true
}
Then, if I want to change the search string and search again I want to empty the dictionary and call reloadData() again I get an app crash.
The error is
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist:
Here is my datasource implementation
var searchResults = [[String: Any]]()
let layout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
layout.estimatedItemSize.height = 200
layout.estimatedItemSize.width = 200
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
return layout
}()
collectionView = UICollectionView(frame: self.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(LanesCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.backgroundColor = .yellow // Constants.APP_BACKGROUND_COLOR
collectionView.alwaysBounceVertical = true
collectionView.clipsToBounds = true
collectionView.translatesAutoresizingMaskIntoConstraints = false
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if searchResults.count == 0 {
collectionView.alpha = 0.0
} else {
collectionView.alpha = 1.0
}
return searchResults.count
}
after query
func parseMoreData(jsonData: [String: Any]) {
let items = jsonData["items"] as! [[String: Any]]
self.collectionView.layoutIfNeeded()
self.collectionView.reloadData()
}
}
func searchFieldValueChanged(_ textValue: String) {
searchResults = []
This looks fixed by using this instead of layoutIfNeeded()
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()

Unexpectedly found nil while unwrapping an Optional value in Swift 3

I have a timetable app, and after converting everything to Swift 3, one particular line threw an EXC_BAD_INSTRUCTION error, stating "Unexpectedly found nil while unwrapping an Optional value"
Here is the code, the final line returns the error:
class CyclicDay {
enum CyclicDayError: Error {
case invalidStartDate }
lazy var baseline: Date! = {
var components = DateComponents()
components.day = 27
components.month = 3
components.year = 2017
return Calendar.current.date(from: components)!
}()
func dayOfCycle(_ testDate: Date) throws -> Int {
if let start = baseline {
let interval = testDate.timeIntervalSince(start as Date)
let days = interval / (60 * 60 * 24)
return Int(days.truncatingRemainder(dividingBy: 14)) + 1 }
throw CyclicDayError.invalidStartDate }}
override func viewDidLoad() {
// Do any additional setup after loading the view, typically from a nib.
let cd = CyclicDay()
let day = try! cd.dayOfCycle(Date())
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute], from: date)
let hour = components.hour
let minutes = components.minute
_ = "\(String(describing: hour)):\(String(describing: minutes))"
let lengthTestHour = "\(String(describing: hour))"
let lengthTestMinute = "\(String(describing: minutes))"
let formatter = DateFormatter()
formatter.dateFormat = "a"
formatter.amSymbol = "AM"
formatter.pmSymbol = "PM"
let dateString = formatter.string(from: Date())
var finalHour = String()
if lengthTestHour.characters.count == 1 {
finalHour = String("0\(String(describing: hour))")
} else {
finalHour = "\(String(describing: hour))"
}
if lengthTestMinute.characters.count == 1 {
_ = "0\(String(describing: minutes))"
} else {_ = minutes }
let convert = finalHour
let mTime = Int(convert)
// mTime * 100 + minutes
let compTime = mTime! * 100 + minutes!
In Swift 3 all date components are optional, you need to unwrap the optionals
let hour = components.hour!
let minutes = components.minute!
otherwise you get in trouble with the string interpolations.
Btw: You don't need String(describing just write for example
_ = "\(hour):\(minutes)"
I'm wondering anyway why you do all the formatting stuff manually instead of using the date formatter you created.
The problem lies in these two lines:
let lengthTestHour = "\(String(describing: hour))"
let lengthTestMinute = "\(String(describing: minutes))"
You thought lengthTestHour will store a value like "7" and lengthTestMinute will have a value like "33". But no, lengthTestHours actually holds "Optional(7)" and lengthTestMinutes actually holds "Optional(33)".
You then assign lengthTestHour to convert and try to convert that Optional(7) thing into an Int, which obviously can't be done. Now mTime is nil and you try to force unwrap in the last line. BOOM!
This is because String(describing:) returns an optional. The two lines can be shortened and fixed by doing:
let lengthTestHour = "\(hour!)"
let lengthTestMinute = "\(minute!)"

FSCalendar events in Swift 3

How can events be added to an FSCalendar in swift 3?
Implement the appropriate methods in a class adopting FSCalendarDataSource.
var datesWithEvent = ["2015-10-03", "2015-10-06", "2015-10-12", "2015-10-25"]
var datesWithMultipleEvents = ["2015-10-08", "2015-10-16", "2015-10-20", "2015-10-28"]
fileprivate lazy var dateFormatter2: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter2.string(from: date)
if self.datesWithEvent.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
Based On FsCalendar Documentation