I have been trying to create an app that randomly prints a new word after a certain amount of time, i.e five seconds.
import UIKit
class ViewController: UIViewController {
var myTimer : Timer!
var theValue = 0
#IBOutlet weak var textLabel: UILabel!
#IBOutlet weak var InspireLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib
}
override func viewDidDisappear(_ animated: Bool) {
self.myTimer.invalidate()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#objc func updateMyLabel() {
theValue += 1
self.textLabel.text = String(theValue)
}
func randomText() -> String {
if theValue > 5 {
let words = ["Place", "Cat", "House"]
return words.randomElement() ?? "" //Use default method to get random element from Array
}
return "string if theValue < 5 or equal to 5"
}
#IBAction func ResetButton(_ sender: Any) {
self.myTimer.invalidate()
theValue = 0
self.textLabel.text = String(theValue)
}
#IBAction func PauseButton(_ sender: Any) {
self.myTimer.invalidate()
}
#IBAction func PlayButton(_ sender: Any) {
self.myTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateMyLabel), userInfo: nil, repeats: true)
}
}
This is all the code I have right now. As you can probably tell I'm new too StackExchange and so please excuse any breach in etiquette.
it's giving an error because you forgot to add else part in your code.
func randomText() -> String {
if theValue > 5 {
let words = ["Place", "Cat", "House"]
return words.randomElement() ?? "" //Use default method to get random element from Array
}
return "string if theValue < 5 or equal to 5"
}
Related
I'm following a tutorial on CoreData and I've been following it exactly, yet when they run the app, everything works and saves correctly, yet I get a nil error. The tutorial is a few years old, so I'm not sure if something has been udpated in the way CoreData works. It's an app to save goals.
Here's the first view controller where you enter the text of the goal and if it is short or long term:
import UIKit
class CreateGoalViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var goalTextView: UITextView!
#IBOutlet weak var shortTermButton: UIButton!
#IBOutlet weak var longTermButton: UIButton!
#IBOutlet weak var nextButton: UIButton!
var userGoalType: GoalType = .shortTerm
override func viewDidLoad() {
super.viewDidLoad()
nextButton.bindToKeyboard()
shortTermButton.setSelectedColor()
longTermButton.setDeselectedColor()
print("\(userGoalType)")
goalTextView.delegate = self
}
#IBAction func nextButtonPressed(_ sender: Any) {
if goalTextView.text != "" && goalTextView.text != "What is your goal?" {
guard let finishVC = storyboard?.instantiateViewController(withIdentifier: "FinishVC") as? FinishGoalViewController else {return}
finishVC.initData(description: goalTextView.text!, type: userGoalType)
print("\(finishVC.goalType.rawValue) after next button pressed")
performSegue(withIdentifier: "goToFinish", sender: self)
}
}
#IBAction func longTermButtonPressed(_ sender: Any) {
userGoalType = .longTerm
longTermButton.setSelectedColor()
shortTermButton.setDeselectedColor()
print("\(userGoalType)")
}
#IBAction func shortTermButtonPressed(_ sender: Any) {
userGoalType = .shortTerm
shortTermButton.setSelectedColor()
longTermButton.setDeselectedColor()
print("\(userGoalType)")
}
#IBAction func backButtonPressed(_ sender: Any) {
dismiss(animated: true)
}
func textViewDidBeginEditing(_ textView: UITextView) {
goalTextView.text = ""
goalTextView.textColor = UIColor(ciColor: .black)
}
}
And here's the following view controller where you set the number of times you want to do that goal where the CoreData functions are:
import UIKit
import CoreData
class FinishGoalViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var createButton: UIButton!
#IBOutlet weak var pointsTextField: UITextField!
var goalDescription: String!
var goalType: GoalType!
func initData(description: String, type: GoalType) {
self.goalDescription = description
self.goalType = type
}
override func viewDidLoad() {
super.viewDidLoad()
createButton.bindToKeyboard()
pointsTextField.delegate = self
}
#IBAction func createGoalPressed(_ sender: Any) {
if pointsTextField.text != ""{
self.save { finished in
if finished {
dismiss(animated: true)
}
}
}
}
#IBAction func backButtonPressed(_ sender: Any) {
dismiss(animated: true)
}
func save(completion: (_ finished: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else {return}
let goal = Goal(context: managedContext)
goal.goalDescription = goalDescription
goal.goalType = goalType.rawValue
goal.goalCompletionValue = Int32(pointsTextField.text!)!
goal.goalProgress = Int32(0)
do{
try managedContext.save()
print("successfully saved data")
completion(true)
}catch{
debugPrint("Could not save: \(error.localizedDescription)")
completion(false)
}
}
}
I'm getting a nil error in the save function with the goalType.rawValue turning up nil. The goal type is set up in an enum file:
import Foundation
enum GoalType: String {
case longTerm = "Long Term"
case shortTerm = "Short Term"
}
I'm not sure why there's an error. Because in the CreateGoalViewController, I print the goalType.rawValue from the following view controller and it comes up with the correct string, either short or long-term. But when FinishGoalViewController loads, it is all of a sudden nil.
You are initiating and configuring your FinishGoalViewController in nextButtonPressed but you never use it. performSegue(withIdentifier: "goToFinish", sender: self) will create and push a new instance of FinishGoalViewController.
The most simple aproach would be to push your allready configured controller from your curent Controller. Remove performSegue(... and use.
self.navigationController?.pushViewController(finishVC, animated: true)
If you still want to use the segue, remove everything from the nextButtonPressed function, leaving just the performSegue(... line. After that add this function to your CreateGoalViewController controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToFinish" {
if let finishVC = segue.destination as? FinishGoalViewController {
// configure finshVC here
}
}
}
I am currently working on an app and I am stuck on the following: I have my mainVC (ReceiveInputVC), which after I enter an input, it goes to the secondVC (TimeLeftVC) and it updates all of its labels with results from the inputs received from the mainVC. My question is: How can I, after clicking on the arrow to go back to the mainVC or even if I close the app, when I click on the arrow from the mainVC to go to the secondVC have my labels showing the same values as before the user closed the application or returned to the main screen?
import UIKit
extension UIViewController {
func hideKeyboard() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
class ReceiveInputVC: UIViewController {
#IBOutlet weak var hourglassButton: UIButton!
#IBOutlet weak var whatIsYourAgeField: UITextField!
#IBOutlet weak var ageToDieField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.hideKeyboard()
}
#IBAction func arrowBtnPressed(_ sender: Any) {
// When pressed should show go to TimeLeftVC and show last result from the first time user entered the inputs, if nothing has been typed yet and no data has been saved an alert should pop up asking the user to enter an input on both fields
}
#IBAction func hourglassBtnPressed(_ sender: Any) {
let checkAgeField: Int? = Int(whatIsYourAgeField.text!)
let checkDyingAgeField: Int? = Int(ageToDieField.text!)
if (whatIsYourAgeField.text == "" || ageToDieField.text == "") || (whatIsYourAgeField.text == "" && ageToDieField.text == "") {
alert(message: "You must enter an input on both fields")
} else if checkAgeField! < 1 || checkDyingAgeField! > 100 {
alert(message: "You must enter an age higher than 1 and a dying age lower than 100")
} else if (checkAgeField! > checkDyingAgeField!) || (checkAgeField! == checkDyingAgeField!) {
alert(message: "You must enter an age lower than a dying age")
} else {
performSegue(withIdentifier: "goToSecondScreen", sender: self)
}
}
func alert(message: String, title: String = "Alert") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Try Again", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// Passing the data entered from ReceiveInputVC to TimeLeftVC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSecondScreen" {
let destinationTimeLeftVC = segue.destination as! TimeLeftVC
destinationTimeLeftVC.ageReceived = whatIsYourAgeField.text
destinationTimeLeftVC.ageToDieReceived = ageToDieField.text
}
}
}
import CircleProgressBar
class TimeLeftVC: UIViewController {
var ageReceived: String! // receive whatIsYourAgeField data from ReceiveInputVC
var ageToDieReceived: String! // receive ageToDieField data from ReceiveInputVC
#IBOutlet weak var yearsLeftLabel: UILabel!
#IBOutlet weak var daysLeftLabel: UILabel!
#IBOutlet weak var hoursLeftLabel: UILabel!
#IBOutlet weak var progressBar: CircleProgressBar!
override func viewDidLoad() {
super.viewDidLoad()
createResults()
}
func createResults() {
if let userAge = Int(ageReceived), let dyingAge = Int(ageToDieReceived) {
let yearsLeft = dyingAge - userAge
let daysLeft = yearsLeft * 365
let hoursLeft = daysLeft * 24
// Update UI
yearsLeftLabel.text = "\(yearsLeft)"
daysLeftLabel.text = "\(daysLeft)"
hoursLeftLabel.text = "\(hoursLeft)"
let percentage = (CGFloat(yearsLeft) / CGFloat(dyingAge)) * 100
let formatted = String(format: "%.1f", percentage)
// Update Circle Progress Bar
progressBar.setHintTextGenerationBlock { (progress) -> String? in
return String.init(format: "\(formatted)%%", arguments: [progress])
}
progressBar.setProgress(percentage/100, animated: true, duration: 4.0)
}
}
Project on GitHub: https://github.com/mvvieira95/Time-Life.git
You can use Coredata or another data base or user default
User default implementation:
#IBAction func arrowBtnPressed(_ sender: Any) {
UserDefaults.standard.set("your input values from text field or ...", forKey: "key")
}
In second view controller get it with
UserDefaults.standard.string(forKey: "key")
You can save and restore states with these methods
application:shouldSaveApplicationState and application:shouldRestoreApplicationStat.
Example:
func application(_ application: UIApplication,
shouldSaveApplicationState coder: NSCoder) -> Bool {
// Save the current app version to the archive.
coder.encode(11.0, forKey: "MyAppVersion")
// Always save state information.
return true
}
func application(_ application: UIApplication,
shouldRestoreApplicationState coder: NSCoder) -> Bool {
// Restore the state only if the app version matches.
let version = coder.decodeFloat(forKey: "MyAppVersion")
if version == 11.0 {
return true
}
// Do not restore from old data.
return false
}
You can explore the document in https://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches?language=objc
Thanks guys, I came up with a solution:
class ReceiveInputVC: UIViewController {
#IBAction func arrowBtnPressed(_ sender: Any) {
let defaults = UserDefaults.standard
if let _ = defaults.object(forKey: "yearsSaved"), let _ = defaults.object(forKey: "daysSaved"), let _ = defaults.object(forKey: "hoursSaved") {
performSegue(withIdentifier: "goToSecondScreen", sender: self)
} else {
alert(message: "You must first enter an input")
}
}
class TimeLeftVC: UIViewController {
var ageReceived: String! // receive whatIsYourAgeField data from ReceiveInputVC
var ageToDieReceived: String! // receive ageToDieField data from ReceiveInputVC
#IBOutlet weak var yearsLeftLabel: UILabel!
#IBOutlet weak var daysLeftLabel: UILabel!
#IBOutlet weak var hoursLeftLabel: UILabel!
#IBOutlet weak var progressBar: CircleProgressBar!
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
yearsLeftLabel.text = defaults.object(forKey: "yearsSaved") as? String
daysLeftLabel.text = defaults.object(forKey: "daysSaved") as? String
hoursLeftLabel.text = defaults.object(forKey: "hoursSaved") as? String
}
override func viewWillAppear(_ animated: Bool) {
createResults()
}
func createResults() {
if let userAge = Int(ageReceived), let dyingAge = Int(ageToDieReceived) {
let yearsLeft = dyingAge - userAge
let daysLeft = yearsLeft * 365
let hoursLeft = daysLeft * 24
// Update UI
yearsLeftLabel.text = "\(yearsLeft)"
daysLeftLabel.text = "\(daysLeft)"
hoursLeftLabel.text = "\(hoursLeft)"
// Store Data
let defaults = UserDefaults.standard
defaults.set(yearsLeftLabel.text, forKey: "yearsSaved")
defaults.set(daysLeftLabel.text, forKey: "daysSaved")
defaults.set(hoursLeftLabel.text, forKey: "hoursSaved")
// Update Circle Progress Bar
let percentage = (CGFloat(yearsLeft) / CGFloat(dyingAge)) * 100
let formatted = String(format: "%.1f", percentage)
progressBar.setHintTextGenerationBlock { (progress) -> String? in
return String.init(format: "\(formatted)%%", arguments: [progress])
}
progressBar.setProgress(percentage/100, animated: true, duration: 4.0)
}
}
Having troubles now updating that progressBar when I go back to the view...
I'm using Xcode 8.0, Swift 3.
I'm making today extension showing text and image, but frequently cause this message and today extension does not work.
rarely work.
Program ended with exit code: 0
I saved data in realm, and today extension using the data(string, image)
is it cause a memory limit? so, how can i derease memory about today extension using??
My extension code:
class Information: Object {
dynamic var Name = ""
dynamic var Image : NSData?
override class func primaryKey() -> String {
return "Name"
}
}
class TodayViewController: UIViewController, NCWidgetProviding {
var realm : Realm?
var results : Results<Information>?
var index = 0
#IBOutlet var NameLabel: UILabel!
#IBOutlet var ImageView: UIImageView!
#IBAction func leftAction(_ sender: UIButton) {
guard index == 0 else {
index -= 1
loadData(index: index)
return
}
}
#IBAction func rightAction(_ sender: UIButton) {
guard index == (results?.count)! - 1 else {
index += 1
loadData(index: index)
return
}
}
override func viewDidLoad() {
super.viewDidLoad()
setRealmFileURL()
if #available(iOSApplicationExtension 10.0, *) {
extensionContext?.widgetLargestAvailableDisplayMode = .expanded
}else{
preferredContentSize = CGSize(width: 0, height: 250)
}
// Do any additional setup after loading the view from its nib.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
realm = try! Realm()
results = try! Realm().objects(Information.self)
loadData(index: index)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func widgetPerformUpdate(completionHandler: ((NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
loadData(index: index)
completionHandler(NCUpdateResult.newData)
}
func loadData(index: Int){
guard results?.count == 0 else {
let objects = results?[index]
NameLabel.text = objects?.Name
ImageView.image = UIImage(data: (objects?.Image)! as Data, scale: 0.1)
return
}
}
#available(iOSApplicationExtension 10.0, *)
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
preferredContentSize = CGSize(width: 0, height: 250)
}
func setRealmFileURL(){
var config = Realm.Configuration(encryptionKey: getKey() as Data)
let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.xxx")
let realmPath = directory?.appendingPathComponent("xxx.realm")
config.fileURL = realmPath
Realm.Configuration.defaultConfiguration = config
}
}
mostly it is a problem with memory. The Widget will get cut off if it consumes to much.
Unless something in the app needs to change, don't use
completionHandler(NCUpdateResult.NewData)
instead use
completionHandler(NCUpdateResult.noData)
When developing an iOS app I come across random (sometimes occurring, sometimes not) EXC_BAD_ACCESS error in the following code:
import UIKit
class OrderTripDetailsController: UIViewController, OrderAware {
var order: Order?
var orderService = OrderService()
// MARK: Properties
#IBOutlet weak var driverName: UILabel!
#IBOutlet weak var autoColor: UILabel!
#IBOutlet weak var autoPlates: UILabel!
#IBOutlet weak var autoBrandModel: UILabel!
#IBOutlet weak var map: YMKMapView!
// MARK: Actions
// MARK: Navigation
override func viewDidLoad() {
if let cab = order?.orderCab {
autoColor.text = cab.cab.auto.color.name
autoPlates.text = cab.cab.auto.plates
driverName.text = cab.cab.driver.fullname
autoBrandModel.text = "\(cab.cab.auto.brand) \(cab.cab.auto.model)"
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
orderService.controllerDelegate = self
orderService.startSync()
}
override func viewWillDisappear(animated: Bool) {
orderService.stop() // <-- this is the line that fails
super.viewWillDisappear(animated)
}
// MARK: Order Aware
func setOrder(order: Order){
self.order = order
}
}
OrderService
Order Service is a class that starts timer and stops it. After stop is called, sometimes EXC_BAD_ACCESS occurs
import Locksmith
import RealmSwift
class OrderService {
// MARK: delegates
var controllerDelegate: UIViewController?
// Get the default Realm
let realm = try! Realm()
var timer = NSTimer()
//MARK: Domain actions
//repeat every n time if any dfound
#objc func initOrderSync(timer:NSTimer) {
func onRemoteReceived (order: Order) -> Void {
debugPrint("remote received", order.id)
if let o = realm.objectForPrimaryKey(OrderPo.self, key: order.orderHash) {
if o.orderStatus != order.orderStatus {
debugPrint("order status updated", order.id, order.orderStatus)
OrderUtils.navigateToOrderStatus(order,
viewController: controllerDelegate!)
}
try! realm.write {
o.orderStatus = order.orderStatus
}
}
}
if let authToken = AuthUtils.getToken() {
let orders = getOrdersByStatuses(OrderUtils.activeStatuses)
if orders.count > 0 {
for o in orders {
OrderRemoteService().getOrderFromRemote(o.id!, token: authToken, callback: onRemoteReceived)
}
} else {
debugPrint("invalidating timer; no orders")
stop()
}
} else {
debugPrint("invalidating timer; no auth")
stop()
}
}
func startSync() {
if !self.timer.valid {
debugPrint("starting order sync", controllerDelegate?.restorationIdentifier)
self.timer = NSTimer.scheduledTimerWithTimeInterval(20, target: self, selector: Selector("initOrderSync:"), userInfo: nil, repeats: true)
} else {
debugPrint("sync is already started", controllerDelegate?.restorationIdentifier)
}
}
func stop() {
dispatch_async(GlobalMainQueue, {
if self.timer.valid {
self.timer.invalidate()
debugPrint("invalidated timer", self.controllerDelegate?.restorationIdentifier)
} else {
debugPrint("sync is already stopped", self.controllerDelegate?.restorationIdentifier)
}
})
}
// MARK: orders
func getOrders() -> Results<OrderPo> {
return try! realm.objects(OrderPo.self).sorted("orderTime")
}
func getOrdersByStatuses(statuses: [String]) -> Results<OrderPo> {
var qString = "'\(statuses.first!)'"
if statuses.count > 1 {
for s in 1...statuses.count-1 {
qString += ",'\(statuses[s])'"
}
}
return try! realm.objects(OrderPo.self).filter("orderStatus IN {\(qString)}").sorted("orderTime")
}
}
Could anyone help with any ideas why it might happen?
Update 20.08.2016
Found out that OrderService is being deinitialised after 10 seconds for some reason.
Here's my code for my calculator and it works fine!
import UIKit
class ViewController: UIViewController {
var number1 = 0
var number2 = 0
var operation = 0
#IBOutlet var screen: UILabel!
#IBAction func Onebutton(sender: AnyObject) {
addNumber(1)
}
#IBAction func Twobutton(sender: AnyObject) {
addNumber(2)
}
#IBAction func Threebutton(sender: AnyObject) {
addNumber(3)
}
#IBAction func Fourbutton(sender: AnyObject) {
addNumber(4)
}
#IBAction func Fivebutton(sender: AnyObject) {
addNumber(5)
}
#IBAction func Sixbutton(sender: AnyObject) {
addNumber(6)
}
#IBAction func Sevenbutton(sender: AnyObject) {
addNumber(7)
}
#IBAction func Eightbutton(sender: AnyObject) {
addNumber(8)
}
#IBAction func Ninebutton(sender: AnyObject) {
addNumber(9)
}
#IBAction func Zerobutton(sender: AnyObject) {
addNumber(0)
}
#IBAction func Multiplybutton(sender: AnyObject) {
operation = 1
}
#IBAction func Dividebutton(sender: AnyObject) {
operation = 2
}
#IBAction func Addbutton(sender: AnyObject) {
operation = 3
}
#IBAction func Subtractbutton(sender: AnyObject) {
operation = 4
}
#IBAction func Equalsbutton(sender: AnyObject) {
switch operation{
case 1: screen.text = String (number1*number2)
case 2: screen.text = String (number1/number2)
case 3: screen.text = String (number1+number2)
case 4: screen.text = String (number1-number2)
default: println()
}
}
#IBAction func clearbutton(sender: AnyObject) {
number1 = 0
number2 = 0
operation = 0
screen.text = "0"
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func addNumber (pushButton : Int){
if operation == 0 {
number1 = number1*10+pushButton
screen.text = String (number1)
} else {
number2 = number2*10+pushButton
screen.text = String (number2)
}
}
}
But I want to make the calculator calculate in decimal form and not round off to the nearest number.
Use floats.
var number: Float = 0
number = number + 0.1
println(number)
//0.1 should be printed.
You are currently basically saying:
var number: Int = 0
This is pretty much how the computer reads it; number is an integer. To make it so that number can be a decimal (float), you have to say:
var number: Float = 0