Swift 4: Checking if week, month, 2 months, etc. has passed since initial launch using Calendar - ios

I'm trying to include timers so that a button appears a week, a month, 2 months, etc. after the initial launch date of the app.
I've used Date.addingTimeInterval (see code below), but it doesn't seem as effective for longer periods of time (week/months). The Swift documentation recommends using Calendar, but I'm not sure how to do that.
override func viewDidLoad() {
super.viewDidLoad()
if installedDate == nil {
installedDate = Date()
let timer = Calendar.current
Button1.isHidden = true
Button2.isHidden = true
Button3.isHidden = true
}
else {
print("Not first run")
print(installedDate!)
let Button1Date = Date().addingTimeInterval(604800) //for after 1 week
let Button2Date = Date().addingTimeInterval(2592000) //for after 1 month
let Button3Date = Date().addingTimeInterval(5184000) //for after 2 months
if installedDate == Button1Date {
Button1.isHidden = false
}
if installedDate == Button2Date {
Button2.isHidden = false
}
if installedDate == Button3Date {
Button.isHidden = false
}
}
}
var installedDate: Date? {
get {
return UserDefaults.standard.object(forKey: "installedDateKey") as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: "installedDateKey")
}
}
What is a more efficient way of comparing the launch date to the expected date I need?

From what I understand of your question, you want a button to display if they have had the app for more than a certain amount of time. If that is the case, I've written some code that will be helpful to you:
let timeSinceInstalled = -(installedDate ?? Date()).timeIntervalSinceNow
Button1.isHidden = timeSinceInstalled < 604800
Button2.isHidden = timeSinceInstalled < 2592000
Button3.isHidden = timeSinceInstalled < 5184000
You can actually replace most of the code inside of your viewDidLoad() function, even the part where you check if installedDate == nil. Here is your updated code:
override func viewDidLoad() {
super.viewDidLoad()
let timeSinceInstalled = -(installedDate ?? Date()).timeIntervalSinceNow
Button1.isHidden = timeSinceInstalled < 604800
Button2.isHidden = timeSinceInstalled < 2592000
Button3.isHidden = timeSinceInstalled < 5184000
}
var installedDate: Date? {
get {
return UserDefaults.standard.object(forKey: "installedDateKey") as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: "installedDateKey")
}
}
If you want to set the title to "You have none available" when the button should not be clickable, here is the code for that:
override func viewDidLoad() {
super.viewDidLoad()
let timeSinceInstalled = -(installedDate ?? Date()).timeIntervalSinceNow
setTitle(Button1, timeSinceInstalled < 604800)
setTitle(Button2, timeSinceInstalled < 2592000)
setTitle(Button3, timeSinceInstalled < 5184000)
}
func setTitle(_ button: UIButton, _ statement: Bool) {
button.setTitle(statement ? "You have none available" : "Click me", for: .normal)
button.isEnabled = !statement
}
var installedDate: Date? {
get {
return UserDefaults.standard.object(forKey: "installedDateKey") as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: "installedDateKey")
}
}

Related

Changes in the datepicker widgets in iOS?

In our iOS app we have a timepicker widget to book appointments. This has worked fine for a long time. We just changed iOS developer and when he rebuilt the app the timepicker is broken for some reason we cannot tell. Has something changed recently in XCode/iOS that could explain why the layout suddenly is broken?
How it was
How it is now
This is the code responsible:
This timepicker is based on the standard iOS UIDatePicker.
class TimePickerController: UIViewController {
#IBOutlet weak var notAvailableLbl: UILabel!
#IBOutlet weak var fromPicker: UIDatePicker!
#IBOutlet weak var toPicker: UIDatePicker!
#IBOutlet weak var switchNotAvailable: UISwitch!
var object: EventElement?
var delegate:TimePickerControllerDelegate?
var name: DEFAULT_AVAILABILITY_CONST!
var startTime,endTime: Date?
var isFromSettings:Bool = false
var isNotAvailable: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.barTintColor = AppColors.Primary
switchNotAvailable.setOn(isNotAvailable, animated: true)
switchChanged(switchNotAvailable)
setPickerLocale(withIdentifier: "en_GB")
setPickerTimezone(withIdentifier: "UTC")
initPickerDates()
if object?.type == EVENT_CONST.lunch{
switchNotAvailable.isHidden = true
notAvailableLbl.isHidden = true
}else{
switchNotAvailable.isHidden = false
notAvailableLbl.isHidden = false
}
}
func setPickerLocale(withIdentifier locale: String) {
// Changing to 24 hrs
let local = NSLocale(localeIdentifier: locale) as Locale
fromPicker.locale = local
toPicker.locale = local
}
func setPickerTimezone(withIdentifier timeZone: String) {
//set timezone
fromPicker.timeZone = TimeZone.init(identifier: timeZone)
toPicker.timeZone = TimeZone.init(identifier: timeZone)
}
func initPickerDates() {
if let obj = object {
let startTime = obj.start.split(separator: "Z")
let endTime = obj.end.split(separator: "Z")
if(startTime.first == endTime.first)
{
fromPicker.date = getTimeFromString(stringTime: String("08:00"))
toPicker.date = getTimeFromString(stringTime: String("22:00"))
}
else{
fromPicker.date = getTimeFromString(stringTime: String(startTime.first ?? "08:00"))
toPicker.date = getTimeFromString(stringTime: String(endTime.first ?? "22:00"))
}
}else {
if let start = startTime,let end = endTime {
if(start == end)
{
// 8.00 to 22.00
fromPicker.date = start
toPicker.date = end.dateByAddingHours(hours: 14)
}
else{
fromPicker.date = start
toPicker.date = end
}
}
}
}
func getTimeFromString(stringTime: String) -> Date
{
let local = NSLocale(localeIdentifier: "en_GB") as Locale
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
dateFormatter.locale = local
dateFormatter.timeZone = TimeZone.init(identifier: "UTC")
let date = dateFormatter.date(from: stringTime) ?? Date()
print(date)
return date
}
#IBAction func btnDone(_ sender: Any) {
print(fromPicker.date)
print(toPicker.date)
updateEvent()
}
func updateEvent() {
var start = fromPicker.date.timeDisplay + "Z"
var end = toPicker.date.timeDisplay + "Z"
var createdDate:String = ""
var id:String = ""
var name:String = ""
if object == nil {
start = fromPicker.date.ISOISO8601StringForAvailability + "Z"
end = toPicker.date.ISOISO8601StringForAvailability + "Z"
createdDate = Date().toString(format: DateFormat.Custom("yyyy-MM-dd'T'HH:mm:ss")) + "Z"
id = UUID().uuidString.lowercased()
name = self.name.rawValue
}
else{
createdDate = object?.created ?? ""
id = object?.id ?? ""
name = (object?.name.rawValue) ?? ""
}
print("switch state",switchNotAvailable.isOn)
if switchNotAvailable.isOn {
if let startT = startTime,let endT = endTime {
let startDt = "08:00Z".formatDateWithTime(referenceDate: startT)
let endDt = "08:00Z".formatDateWithTime(referenceDate: endT)
start = startDt!.toString(format: DateFormat.Custom("yyyy-MM-dd'T'HH:mm"),timezone: "UTC") + "Z"
end = endDt!.toString(format: DateFormat.Custom("yyyy-MM-dd'T'HH:mm"),timezone: "UTC") + "Z"
}
else{
start = "08:00Z"
end = "08:00Z"
}
}
ActivityIndicator().showIndicator(backgroundColor: nil)
print("start and end date : ",start," ",end)
let type:String?
if isFromSettings == true{
type = object?.type.rawValue
}
else{
type = "AVAILABILITY_PATCH"
}
print(type ?? "AVAILABILITY_PATCH")
APICalls().updateAvailability(start: start, end: end, name: name, type: type ?? "AVAILABILITY_PATCH", created: createdDate,id: id ) { (response) in
ActivityIndicator().hideIndicator()
if let result = response
{
let res = Event.init(dictionary: result as? NSDictionary ?? [:])
DatabaseManager.getInstance().saveAvailability(object: res)
self.delegate?.didSelectTime(from: self.fromPicker.date, to: self.toPicker.date)
self.navigationController?.popViewController(animated: true)
}
}
}
To disable this time picker style, you have to add below line.
fromPicker.preferredDatePickerStyle = .wheels
In IOS 14 we have to define DatePickerStyle.
Please write these two lines in initPickerDates() method to set picker wheel style
fromPicker.preferredDatePickerStyle = .wheels
toPicker.preferredDatePickerStyle = .wheels
In IOS 14, we have multiple styles for date pickers. So this is the default behavior in iOS 14.
You just need to add the following line in the initPickerDates() method.
fromPicker.preferredDatePickerStyle = .wheels
toPicker.preferredDatePickerStyle = .wheels

How to show dialogue box of rate app after X times?

i made app with swift in Xcode and i have implemented Rate App Modal in my app but the problem is this its shown when users install the app and open for very first time but i want to show it after 2 or 3 days , so users can view my app and if he likes then he can rate my app. this is my code
let reviewService = ReviewService.shared
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let deadline = DispatchTime.now() + .seconds(120)
DispatchQueue.main.asyncAfter(deadline: deadline) { [weak self] in
self?.reviewService.requestReview()
}
}
and this is my ReviewService.swift file
private init() {}
static let shared = ReviewService()
private let defaults = UserDefaults.standard
private let app = UIApplication.shared
private var lastRequest: Date? {
get {
return defaults.value(forKey: "ReviewService.lastRequest") as? Date
}
set {
defaults.set(newValue, forKey: "ReviewService.lastRequest")
}
}
private var oneWeekAgo: Date {
return Calendar.current.date(byAdding: .day, value: -7, to: Date())!
}
private var shouldRequestReview: Bool {
if lastRequest == nil {
return true
} else if let lastRequest = self.lastRequest, lastRequest < oneWeekAgo {
return true
}
return false
}
func requestReview(isWrittenReview: Bool = false) {
guard shouldRequestReview else { return }
if isWrittenReview {
let appStoreUrl = URL(string: "https://itunes.apple.com/app/idxxxxxxx?action=write-review")!
app.open(appStoreUrl)
} else {
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
} else {
// Fallback on earlier versions
}
}
lastRequest = Date()
}
}
When the code finds that no previous request was saved don’t show the request. Instead save that the last request happened 4 or 5 days ago. Then in a few days the first review request will be shown.
One way to do this would be in the shouldRequestReview getter. If lastRequset is nil make a new post dated request time and save that, then return false.

Countdown timer in table view cell shows different values after scrolling

The problem is described in title, but to be more specific here is a full picture.
I have a custom table view cell subclass with label inside it displaying the countdown timer. When there a small portion of timers it works fine, but with a lot of data I need to display timers far beyond the visible cells and when I scroll down fast and then scroll up fast, the timer values in cells start to show different values until a certain point in time, after which it shows the right value.
I tried different variants for those reuseable cells, but I can’t spot a problem. Help needed!!!
Here is the code of implementation of logic.
Custom cell subclass:
let calendar = Calendar.current
var timer: Timer?
var deadlineDate: Date? {
didSet {
updateTimeLabel()
}
}
override func awakeFromNib() {
purchaseCellCardView.layer.cornerRadius = 10
let selectedView = UIView(frame: CGRect.zero)
selectedView.backgroundColor = UIColor.clear
selectedBackgroundView = selectedView
}
override func prepareForReuse() {
super.prepareForReuse()
if timer != nil {
print("Invalidated!")
timer?.invalidate()
timer = nil
}
}
func configure(for purchase: Purchase) {
purchaseSubjectLabel.text = purchase.subject
startingPriceLabel.text = purchase.NMC
stageLabel.text = purchase.stage
fzImageView.image = purchase.fedLaw.contains("44") ? #imageLiteral(resourceName: "FZ44") : #imageLiteral(resourceName: "FZ223")
timeLabel.isHidden = purchase.stage == "Работа комиссии"
warningImageView.image = purchase.warningImage
}
func updateTimeLabel() {
setTimeLeft()
timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in
guard let strongSelf = self else {return}
strongSelf.setTimeLeft()
}
RunLoop.current.add(timer!, forMode: .commonModes)
}
#objc private func setTimeLeft() {
let currentDate = getCurrentLocalDate()
if deadlineDate?.compare(currentDate) == .orderedDescending {
var components = calendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: deadlineDate!)
let dayText = (components.day! == 0 || components.day! < 0) ? "" : String(format: "%i", components.day!)
let hourText = (components.hour == 0 || components.hour! < 0) ? "" : String(format: "%i", components.hour!)
switch (dayText, hourText) {
case ("", ""):
timeLabel.text = String(format: "%02i", components.minute!) + ":" + String(format: "%02i", components.second!)
case ("", _):
timeLabel.text = hourText + " ч."
default:
timeLabel.text = dayText + " дн."
}
} else {
stageLabel.text = "Работа комиссии"
timeLabel.text = ""
timeLabel.isHidden = true
timer?.invalidate()
}
}
private func getCurrentLocalDate() -> Date {
var now = Date()
var nowComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: now)
nowComponents.timeZone = TimeZone(abbreviation: "UTC")
now = calendar.date(from: nowComponents)!
return now
}
deinit {
print("DESTROYED")
timer?.invalidate()
timer = nil
}
The most important part of tableView(_cellForRowAt:)
case .results:
if filteredArrayOfPurchases.isEmpty {
let cell = tableView.dequeueReusableCell(
withIdentifier: TableViewCellIdentifiers.nothingFoundCell,
for: indexPath)
let label = cell.viewWithTag(110) as! UILabel
switch segmentedControl.index {
case 1:
label.text = "Нет закупок способом\n«Запрос предложений»"
case 2:
label.text = "Нет закупок способом\n«Конкурс»"
case 3:
label.text = "Нет закупок способом\n«Аукцион»"
default:
label.text = "Нет закупок способом\n«Запрос котировок»"
}
return cell
} else {
let cell = tableView.dequeueReusableCell(
withIdentifier: TableViewCellIdentifiers.purchaseCell,
for: indexPath) as! PurchaseCell
cell.containerViewTopConstraint.constant = indexPath.row == 0 ? 8.0 : 4.0
cell.containerViewBottomConstraint.constant = indexPath.row == filteredArrayOfPurchases.count - 1 ? 8.0 : 4.0
let purchase = filteredArrayOfPurchases[indexPath.row]
cell.configure(for: purchase)
if cell.timer != nil {
cell.updateTimeLabel()
} else {
search.getDeadlineDateAndTimeToApply(purchase.purchaseURL, purchase.fedLaw, purchase.stage, completion: { (date) in
cell.deadlineDate = date
})
}
return cell
}
And the last piece of a puzzle:
func getDeadlineDateAndTimeToApply(_ url: URL?, _ fedLaw: String, _ stage: String, completion: #escaping (Date) -> ()) {
var deadlineDateAndTimeToApply = Date()
guard stage != "Работа комиссии" else { return }
if let url = url {
dataTask = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error as NSError?, error.code == -403 {
// TODO: Add alert here
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let data = data, let html = String(data: data, encoding: .utf8), let purchasePageBody = try? SwiftSoup.parse(html), let purchaseCard = try? purchasePageBody.select("td").array() else {return}
let mappedArray = purchaseCard.map(){String(describing: $0)}
if fedLaw.contains("44") {
guard let deadlineDateToApplyString = try? purchaseCard[(mappedArray.index(of: "<td class=\"fontBoldTextTd\">Дата и время окончания подачи заявок</td>"))! + 1].text().components(separatedBy: " ") else {return}
dateFormatter.dateFormat = "dd.MM.yyyy HH:mm"
let deadlineDateToApply = deadlineDateToApplyString.first!
let deadlineTimeToApply = deadlineDateToApplyString[1]
guard let deadlineDateAndTimeToApplyCandidate = dateFormatter.date(from: "\(deadlineDateToApply) \(deadlineTimeToApply)") else {return}
deadlineDateAndTimeToApply = deadlineDateAndTimeToApplyCandidate
} else {
guard let deadlineDateToApplyString = try? purchaseCard[(mappedArray.index(of: "<td>Дата и время окончания подачи заявок<br> (по местному времени заказчика)</td>"))! + 1].text().components(separatedBy: " ") else {return}
dateFormatter.dateFormat = "dd.MM.yyyy HH:mm"
let deadlineDateToApply = deadlineDateToApplyString.first!
let deadlineTimeToApply = deadlineDateToApplyString[2]
guard let deadlineDateAndTimeToApplyCandidate = dateFormatter.date(from: "\(deadlineDateToApply) \(deadlineTimeToApply)") else {return}
deadlineDateAndTimeToApply = deadlineDateAndTimeToApplyCandidate
}
DispatchQueue.main.async {
completion(deadlineDateAndTimeToApply)
}
})
dataTask?.resume()
}
}
A few notes:
Tried resetting deadlineDate to nil in prepareForReuse() - doesn’t help;
Using SwiftSoup Framework to parse HTML as you can see in the last code example if it matters.
This is quite a lot of code but from what you are describing your issue is in reusing cells.
You would do well to separate the timers out of the cells and put them inside your objects. It is where they belong (or in some manager like view controller). Imagine having something like the following:
class MyObject {
var timeLeft: TimeInterval = 0.0 {
didSet {
if timeLeft > 0.0 && timer == nil {
timer = Timer.scheduled...
} else if timeLeft <= 0.0, let timer = timer {
timer.invalidate()
self.timer = nil
}
delegate?.myObject(self, updatedTimeLeft: timeLeft)
}
}
weak var delegate: MyObjectDelegate?
private var timer: Timer?
}
Now all you need is is a cell for row at index path to assign your object: cell.myObject = myObjects[indexPath.row].
And your cell would do something like:
var myObject: MyObject? {
didSet {
if oldValue.delegate == self {
oldValue.delegate = nil // detach from previous item
}
myObject.delegate = self
refreshUI()
}
}
func myObject(_ sender: MyObject, updatedTimeLeft timeLeft: TimeInterval) {
refreshUI()
}
I believe the rest should be pretty much straight forward...
Your problem is here:
search.getDeadlineDateAndTimeToApply(purchase.purchaseURL,
purchase.fedLaw,
purchase.stage,
completion: { (date) in
cell.deadlineDate = date
})
getDeadlineDateAndTimeToApply runs asynchronously, calculates something, and then updates the cell.deadlineData in the main thread (which is fine). But in the meantime, while calculating something, the user might have scrolled up and down, the cell might have been reused for another row, and now the update updates the cell incorrectly.
What you need to do is: Do not store the UITableViewCell directly. Instead, keep track of the IndexPath to be updated, and once the caluclation is done, retrieve the the cell that belongs to that IndexPath and update this.

Empty array causing my app to crash in Swift

I keep coming across this error "fatal error: Index out of range", after researching this I am still unsure of exactly how to fix this issue. To give context I have started off with an empty array var playersArray = [UITextField]() so that users can enter their names in order to play the game. I then make sure that the user has or has not entered a value into the text field for each name slot
if let player1Name = name1.text, !player1Name.isEmpty
{ playersArray.append(name1)
} else {
print("Player 1 Empty")
If the player has entered a value into that textfield then ill append that value to the array.
The issue I have is that if I run the game and no user has entered a name into any of the 10 textfields then the game will crash. Im assuming this is because the array is empty?
The error appears on this line where I randomize the names used for the game with the number of elements in the array:
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
I assume if the array is empty then .count will not work?
How can I make sure the game wont crash if the array is empty?
CODE:
var playersArray = [UITextField]()
override func viewDidLoad() {
super.viewDidLoad()
textColor()
question1View.isHidden = true
questionLabel.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Alert message on startup
func alertMessageOnStartUp(){
let alert = UIAlertController(title: "Warning!", message: "Please drink responsibly. By continuing, you agree that you are responsible for any consequences that may result from BottomsUp.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Agree", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// Dismiss keyboard when tapped outside the keyboard
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
// Dimiss keybaord when return button is tapped
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
name1.resignFirstResponder()
name2.resignFirstResponder()
name3.resignFirstResponder()
name4.resignFirstResponder()
name5.resignFirstResponder()
name6.resignFirstResponder()
name7.resignFirstResponder()
name8.resignFirstResponder()
name9.resignFirstResponder()
name10.resignFirstResponder()
return(true)
}
//randomise background colour of each question page
func getRandomBackgroundColor() -> UIColor{
let randomRed:CGFloat = CGFloat(drand48())
let randomGreen:CGFloat = CGFloat(drand48())
let randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
}
func textColor(){
name1.textColor = UIColor.white
name2.textColor = UIColor.white
name3.textColor = UIColor.white
name4.textColor = UIColor.white
name5.textColor = UIColor.white
name6.textColor = UIColor.white
name7.textColor = UIColor.white
name8.textColor = UIColor.white
name9.textColor = UIColor.white
name10.textColor = UIColor.white
}
#IBAction func playButton(_ sender: Any) {
alertMessageOnStartUp()
if let player1Name = name1.text, !player1Name.isEmpty
{ playersArray.append(name1)
} else {
print("Player 1 Empty")
}
if let player2Name = name2.text, !player2Name.isEmpty
{ playersArray.append(name2)
} else {
print("Player 2 Empty")
}
if let player3Name = name3.text, !player3Name.isEmpty
{ playersArray.append(name3)
} else {
print("Player 3 Empty")
}
if let player4Name = name4.text, !player4Name.isEmpty
{ playersArray.append(name4)
} else {
print("Player 4 Empty")
}
if let player5Name = name5.text, !player5Name.isEmpty
{ playersArray.append(name5)
} else {
print("Player 5 Empty")
}
if let player6Name = name6.text, !player6Name.isEmpty
{ playersArray.append(name6)
} else {
print("Player 6 Empty")
}
if let player7Name = name7.text, !player7Name.isEmpty
{ playersArray.append(name7)
} else {
print("Player 7 Empty")
}
if let player8Name = name8.text, !player8Name.isEmpty
{ playersArray.append(name8)
} else {
print("Player 8 Empty")
}
if let player9Name = name9.text, !player9Name.isEmpty
{ playersArray.append(name9)
} else {
print("Player 9 Empty")
}
if let player10Name = name10.text, !player10Name.isEmpty
{ playersArray.append(name10)
} else {
print("Player 10 Empty")
}
question1View.isHidden = false
question1View.backgroundColor = getRandomBackgroundColor()
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
#IBAction func nextQuestionButton(_ sender: Any) {
question1View.backgroundColor = getRandomBackgroundColor()
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
}
Breaking this down:
Int(arc4random_uniform(UInt32(playersArray.count)))
This line gets a random number with a minimum value of 0 and a maximum value of the length of the playersArray minus 1.
I'm actually not sure what it does when the argument you pass in is 0, but it doesn't really matter, as we'll see next.
Then you use that random value here:
playersArray[thatRandomNumber]
Because there are no elements in playersArray, no matter what the value is of thatRandomNumber, it's going to be out of bounds.
You probably want something more like this:
let RandomPlayer = <some default value>
if !playersArray.isEmpty {
RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
}
EDIT
Your latest code still doesn't seem to do anything to prevent indexing into the empty array.
You have:
#IBAction func playButton(_ sender: Any) {
...
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
You need:
#IBAction func playButton(_ sender: Any) {
...
if playersArray.isEmpty {
// do something about that
} else {
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
}
playersArray.count for an empty array is 0, so you are trying to access playersArray[0] - but the array is empty, so nothing exists at the 0 index.
You should do something like this:
let randomPlayer: Player
if !playersArray.isEmpty {
randomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
} else {
randomPlayer = Player() //create a fallback player
}
Alternatively you could make randomPlayer an optional, rather than providing a fallback value. Depends on your needs for that variable.

How do I set up the buttons that are linked to didPressNumber to add to each other when pressed

How do I set up the buttons that are linked to didPressNumber to add to each other when pressed so lets say its a calculator and I want set it up where each button is pressed has a letter and number value when it is pressed it adds to the previous one press and I want to set up 2 labels one displaying the number value and one displaying the letter value and how would I set up the value of each number?
enum modes {
case not_set
case addition
case subtraction
case equals
}
#IBAction func didPressNumber(_ sender: UIButton) {
let stringValue:String? = sender.titleLabel?.text
if (lastButtonWasMode) {
lastButtonWasMode = false
labelString = "0"
}
labelString = labelString.appending(stringValue!)
updateText()
}
func updateText() {
guard let labelInt:Int = Int(labelString) else {
return
}
if (currentMode == .not_set) {
savedNum = labelInt
}
let formatter: NumberFormatter = NumberFormatter()
formatter.numberStyle = .decimal
let num:NSNumber = NSNumber(value: labelInt)
label.text = formatter.string(from: num)
}
func changeMode(newMode:modes) {
if (savedNum == 0) {
return
}
currentMode = newMode
lastButtonWasMode = true
}

Resources