My app consists of 3 ViewControllers. ViewController1 consists of a few labels and buttons where the user tries to solve a problem and presses a button. The button takes them to ViewController 2, where they are shown their stats (accuracy, time, etc.). The app then automatically navigates the user back to the game after 2 seconds. This process goes on for 2 minutes. After the 2 minutes is up, I want the app to navigate to a ViewController 3 (Main Menu).
I'm trying to implement a timer that that keeps going even when switching between view controllers. I've implemented (with the help of stackoverflow) a singleton class that has a timer property. However, it's not functioning as required. I've tried printing out values(commented in the code) to see what's going on, but no luck.
Here's my code:
ApplicationManager.m
//
// ApplicationManager.m
#import <Foundation/Foundation.h>
#import "ApplicationManager.h"
#implementation ApplicationManager
static ApplicationManager* appMgr = nil;
+(ApplicationManager*)instance{
#synchronized ([ApplicationManager class]){
if (!appMgr) {
appMgr = [[self alloc]init];
}
return appMgr;
}
return nil;
}
+(id) alloc{
#synchronized([ApplicationManager class]){
NSAssert((appMgr == nil), #"Only one instance of singleton class may be instantiated");
appMgr = [super alloc];
return appMgr;
}
}
+(id) init{
if (!(self == [super init])) {
return nil;
}
return self;
}
#end
ApplicationManager.h
#interface ApplicationManager : NSObject
+(ApplicationManager*) instance;
#property(weak, nonatomic) NSTimer* timer;
#property(weak, nonatomic) NSDate* startDate;
#end
ViewController.m
#import "ViewController.h"
#import "ApplicationManager.h"
NSString* const MAXTRIALTIME = #"00:50.000"; //50 Seconds just for testing purposes
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[[ApplicationManager instance]setstartDate:[NSDate date]];
[self startTimer];
//other functionalities
}
-(void)startTimer{
[[ApplicationManager instance] setTimer:[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTimer) userInfo:nil repeats:YES]];
}
-(void) resetTimer{
[[[ApplicationManager instance] timer]invalidate];
}
-(void)updateTimer{
NSDate *currentTime = [NSDate date];
NSLog(#"Start Date2: %#", [[ApplicationManager instance]startDate]); //PRINTS (null)
NSTimeInterval timerInterval = [currentTime timeIntervalSinceDate:[[ApplicationManager instance]startDate]];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timerInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"mm:ss.SSS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
self.finalTime = [dateFormatter stringFromDate:timerDate];
NSLog(#"Time: %#\n",self.finalTime); //PRINTS random numbers
if ([self.finalTime isEqualToString:MAXTRIALTIME]) {
[self resetTimer];
//move to a different view controller
}
}
#end
ViewController.h
extern NSString* const MAXTRIALTIME;
#property (strong, nonatomic) NSString *finalTime;
//other declarations
In your ApplicationController singleton class you are making a call to self in the instance method. Any methods that instantiate a class (i.e. Methods that begin with a +) should never reference self as self will not exist at that point.
You need to do:
if (!appMgr) {
appMgr = [[ApplicationManager alloc] init];
}
This is the reason you are getting NULL when you print the startDate property.
Also, the ApplicationManager singleton is the owner of its timer property so that needs to be (strong). Weak properties are for references to objects that are owned by other classes. Check out ARC stuff.
Lastly the init method should be a - method not a + one and You don't need to override the alloc method as long as you only use the instance method to access your ApplicationManager. If I were you I'd get rid of the alloc and init methods altogether and just use the instance one.
Related
I am starting a watch development project. It will basically be starting and stopping several timers on the watch. The initial code works fine on the 42mm Simulator but not on the actual watch. That is, tapping on the Start button begins the WKInterfaceTimer label running. Nothing happens on the physical watch. However, I know the timer is working because if I check Enabled in the InterfaceBuilder the timer starts incrementing as soon as the app launches on the watch. Here is the code for watch app extension.h and .m. The outlet is connected properly in IB. Also, tapping on the Dispatch button enters the Dispatch IBAction and works correctly. I have also tried powering off and powering on the watch. That does not help.
I would really appreciate some help on this.
interface controller.h
/
/
/
/
/
/
#import <WatchKit/WatchKit.h>
#import <Foundation/Foundation.h>
#interface InterfaceController : WKInterfaceController
#property (unsafe_unretained, nonatomic) IBOutlet WKInterfaceTimer *ElapsedTime;
#property (unsafe_unretained, nonatomic) IBOutlet WKInterfaceLabel *dispatchTime;
#property (weak, nonatomic) NSDate *startDate;
#end
/
/
/
interface controller.m
/
/
/
/
#import "InterfaceController.h"
#interface InterfaceController()
#end
#implementation InterfaceController
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
/
}
- (void)willActivate {
/
[super willActivate];
}
- (void)didDeactivate {
/
[super didDeactivate];
}
- (IBAction)Start {
self.startDate = [NSDate date];
[self.ElapsedTime setDate:self.startDate];
[self.ElapsedTime start];
}
- (IBAction)Stop {
[self.ElapsedTime stop];
}
- (IBAction)Dispatch {
NSDate *date =[NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterNoStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
NSString *dateString = [dateFormatter stringFromDate:date];
[self.dispatchTime setText:dateString];
}
#end
Still not sure why the WKInterfaceTimer object doesn't work. Instead, I used a standard NSTimer object and a standard NSDate object. The code works. Note: if you have another page-based storyboard scene that the first scene seques to, you have to add a custom WkInterfaceController object to the next scene and connect your outlets from the interface storyboard to it. Otherwise, the outlets won't connect because the file's owner isn't correct. Also, make sure that you add the custom WkInterfaceController to the watch kit extension target, not the iOS target.
//
// InterfaceController.h
// EMSTimers WatchKit 1 Extension
//
// Created by Nelson Capes on 12/18/15.
// Copyright © 2015 Nelson Capes. All rights reserved.
//
#import <WatchKit/WatchKit.h>
#import <Foundation/Foundation.h>
#interface InterfaceController : WKInterfaceController
#property (weak, nonatomic) IBOutlet WKInterfaceLabel *ElapsedTime;
#property (strong, nonatomic) IBOutlet WKInterfaceLabel *dispatchTime;
#property (strong, nonatomic) NSDate *startDate;
#property (strong, nonatomic) NSDate *dateStarted;
#property (strong, nonatomic) NSTimer *elaspsedTimer;
#end
//
// InterfaceController.m
// EMSTimers WatchKit 1 Extension
//
// Created by Nelson Capes on 12/18/15.
// Copyright © 2015 Nelson Capes. All rights reserved.
//
#import "InterfaceController.h"
#interface InterfaceController()
#end
#implementation InterfaceController
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
}
- (IBAction)Start {
NSDate *date =[NSDate date];
self.dateStarted = date;
NSTimeInterval timeInterval = 1.0;
self.elaspsedTimer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:#selector(calculateTimer:) userInfo:self.elaspsedTimer repeats:YES];
[self.elaspsedTimer fire];
}
- (IBAction)Stop {
[self.elaspsedTimer invalidate];
}
-(void)calculateTimer:(NSTimer *)theTimer
{
NSTimeInterval interval = [self.dateStarted timeIntervalSinceNow];
interval = (-1 * interval);
int time = round(interval);
div_t h = div(time, 3600); //seconds total, divided by 3600 equals
int hours = h.quot; // hours, divided by 60 equals
div_t m = div(h.rem, 60); // minutes
int minutes = m.quot;
int seconds = m.rem; // and remainder is seconds
NSString *intervalString = [NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
[self.ElapsedTime setText:intervalString];
}
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];
}
- (void)didDeactivate {
// This method is called when watch view controller is no longer visible
[super didDeactivate];
}
#end
I asked a question:link
After discussing this issue, I will rewrite the original program, become Singleton methods.
I'll call timer which way to write in MySingleton,then TimerViewController inside to call MySingleton's method :
MySingleton.h
#interface MySingleton : NSObject
{
int remainder;
NSTimer *timer;
NSTimeInterval countDownInterval;
int hours;
int mins;
int secs;
}
#property(nonatomic, retain)NSString *displayText;
#property int afterRemainder;
+(MySingleton*) getInstance;
-(void)doSomethingWithString:(int*)parameter;
#end
MySingleton.m
-(void)doSomethingWithString:(int*)parameter {
remainder = *parameter;
_afterRemainder = *parameter - remainder % 60 ;
timer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateCountDown)userInfo:nil repeats:YES];
}
-(void)updateCountDown{
_afterRemainder --;
hours = (int)(_afterRemainder/(60*60));
mins = (int)(((int)_afterRemainder/60)-(hours * 60));
secs = (int)(((int)_afterRemainder - (60 * mins) - (60 * hours *60)));
displayText = [[NSString alloc]initWithFormat:#"%02u : %02u : %02u",hours,mins,secs];
}
TimerViewController.h
#interface TimerViewController : UIViewController
#property (retain, nonatomic) IBOutlet UIDatePicker *CountDown;
#property (retain, nonatomic) IBOutlet UILabel *LabTimer;
#property (retain, nonatomic) IBOutlet UIButton *BtnStart;
- (IBAction)StartCmd:(id)sender;
#end
TimerViewController.m
Here to call timer,UILabel is placed TimerViewController.m
- (IBAction)StartCmd:(id)sender {
MySingleton* singleton = [MySingleton getInstance];
nowInterval=(NSTimeInterval)CountDown.countDownDuration;
nowTimer = nowInterval;
[singleton doSomethingWithString:&(nowTimer)];
}
TimerViewContorller have one Label call LabTimer, MySingleton have a NSString call display text I want to call LabTimer in MySingleton,and displays displaytext in LabTimer
I don't know how to do it?
don't understand u're question. But show u how singleton may work.
so:
u have some Singleton class: MySingleton.
h.file
#interface MySingleton : NSObject {
}
+ (id)sharedManager;
- (void)SomeMethod:(int)someParameter;
#end
m file
#implementation MySingleton
+ (id)sharedManager {
static MySingleton *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
#pragma mark - Some public Method
- (void)SomeMethod:(int)someParameter
{
// code here
[self somePrivateMethod];
}
#pragma mark - Some private Method
- (void)somePrivateMethod {
// code here
}
And u can use this class inside u're MainClassController:
like this:
// some code before
/* call singleton `Some Method` */
[[MySingleton sharedManager] SomeMethod:'int value'];
// code after
Maybe its helps u . Good luck)
You need to set the text of the UILabel when the timer fires. Move the assignment statement that assigns displayText into the timer's action method (updateCountDown).
By the way, I don't understand UILabel.text = ... I assume that this is a syntax error in your code.
When you do an assignment like this:
myLabel.text = displayText;
what happens is that the right-hand side expression (a single variable in this case) is evaluated and its value at the time the statement is executed is assigned to the text property of the UILabel (and the user sees it). If you change the value of displayText later, that will make no difference to what the user sees - the assignment to the text property has to be executed again. I hope this is clear.
To solve your problem:
Add a weak reference to TimeViewController to MySingleton:
#property (weak) TimeViewController* myViewController;
Before calling doSomethingWithString, assign self to singleton
MySingleton* singleton = [MySingleton getInstance];
nowInterval=(NSTimeInterval)CountDown.countDownDuration; nowTimer =
nowInterval; singleton.myViewController = self; [singleton
doSomethingWithString:&(nowTimer)];
Whenever the timer fires, call a method of TimeViewController
... displayText = [[NSString alloc]initWithFormat:#"%02u : %02u :
%02u",hours,mins,secs]; [self.myViewController
updateDisplay:displayText];
I am creating a custom table-view cell generator and I want to pass in a reference to an Object created in a base class which will be mutated in a separate VC while the originating object class will reflect these changes:
//MutableTableViewObjects.h
#interface MutableTableViewObjects : NSObject
#property (retain, atomic) NSDate *startDate;
//MutableTableViewObjects.m
- (id) init {
self = [super init];
if (self) {
self.startDate = [[NSDate alloc] init];
}
return self;
}
then I want a call in a separate view controller to change the value of startDate
self.objectClass = [[MutableTableViewObjects alloc] init];
DateOptionCellInput *startDateCell = [[DateOptionCellInput alloc] initDateInputForObject:self.objectClass.startDate withDefault:[NSDate date] withTitle:#"start date" inSection:#"Dates"];
From here I want startDateCell to modify the value of startDate and have self.objectClass.startDate reflect the changes happening to its value in other classes/threads
Is this even possible or should I seriously reconsider the architecture?
Currently the value changes in the VC but it is never updated in the base object class. Is there some property parameters I can pass in to allow this?
edit: here is how date cell will
#interface BaseOptionCellInput : NSObject
#property (atomic) NSObject* observedObject;
- (id) initType:(NSString*) optionType withTitle:(NSString*) titleString inSection:(NSString*) sectionHeaderString {
self = [super init];
if (self) {
self.title = titleString;
self.sectionHeader = sectionHeaderString;
self.identifier = optionType;
}
return self;
}
- (void) setManagedObject:(NSObject*) managedObject withDefaultValue:(NSObject*) defaultvalue {
self.observedObject = managedObject;
self.defaultValue = defaultvalue;
}
- (void) updateContextWithValue:(NSObject*) newValue {
if ([self.observedObject class] == [newValue class]) {
self.observedObject = newValue;
}
}
and then DateCell is a subclass of BaseCell and calls updateContextWithValue from a delegate method triggered in the VC.
I know I can accomplish this if I used a dictionary to pass the values of objects back and forth but I was hoping it was possible just passing a pointer to the object?
I imagine in initDateInputForObject function you are just using the value from the passed in object and assigning it to startDateCell's member variables.
What you want to do is probably keep a pointer to the MutableTableViewObjects object so you can directly modify the date value in there.
If you can add the code for MutableTableViewObjects I can elaborate with better clarity rather than shoot arrows in the dark :)
But hopefully you get the idea of what I am saying. Point being that no where in your code the orig MutableTableViewObjects object values are being updated.. Just because you used it to initialize another object doesn't mean it will stay updated when you change second object.
EDIT:
Lets say I have an class A with NSDate*
A.h
#interface A : NSObject
#property NSDate *startDate;
}
with A.m
- (id) init {
self = [super init];
if (self) {
self.startDate = [[NSDate alloc] init];
}
return self;
}
Now there is a class B as follows that contains an object of A.
B.h
#interface B : UITableViewController
#property (strong, nonatomic) A *aObject;
#end
and B.m
- (void) some function {
self.aObject = [[A alloc] init];
}
Now if you change self.aObject.startDate, you should be able to change the value of data in A directly.
If its not working as expected, tell me exactly what it is and where it is happening
thx
Okay, I am building an app for iOS and I am having some trouble with getting the current time to display properly within a UILabel.
Here is the code in the ViewController.h file:
#interface ViewController : UIViewController <UIApplicationDelegate>
#property (strong, nonatomic) IBOutlet UILabel *timeLabelStandbyScreen;
-(void)updateLabel;
-(void)updateTime;
#end
#interface updateTime : NSDate
#end
I'm new to Obj-C and I was playing around with it to see if I could fix the problem. Everything is fine in this file it's the .m that has the problems.
ViewController.m:
#interface ViewController ()
#end
#implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self updateLabel];
}
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)updateTime
{
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"hh:mm"];
_timeLabelStandbyScreen.text = [dateFormat stringFromDate: [NSDate date]];
[self preformSelector:#selector(updateTime) withObject:self afterDelay:1.0];
}
#end
I'm sure the problem is really simple and I appreciate your help. Thanks!
By commenting out the preformSelector method and adding a NSTimer (like #rocir suggested) it correctly displayed the time.
I'm having a problem getting an NSTimer to work, probably because I don't know how to properly use it (I've tried reading the apple documentation, it didn't help me much). I get a time off of a UIDatePicker and I want the app to call the method when that time is reached. Simple, no? So the code I have is below:
-(IBAction)SetButtonPress:(id)sender{
NSDate *date = [Picker date];
alarmTimer = [[AlarmTimer alloc] init];
[alarmTimer runTimer: Picker.date];
}
A few things now follow standards, but it still doesn't work. It still gives me a time 6 hours ahead, and when it hits that time, it doesn't do anything.
And the code for the RunTimer method of my alarmTimer class is as follows:
-(void)RunTimer: (NSDate *) date
{
NSRunLoop *theLoop = [NSRunLoop currentLoop];
[theLoop addTimer:timer forMode:NSDefaultRunLoopMode];
[timer initWithFireDate:date interval:0 target:self selector:#selector(Play) userInfo: nil repeats:NO];
}
A few things now follow standards, but it still doesn't work. It still gives me a time 6 hours ahead, and when it hits that time, it doesn't do anything.
And just in case it helps, here is the .h and .m files for the view controller:
.h:
//
// Assignment_1ViewController.h
// Assignment 1
//
// Created by Jack Schaible on 11-09-28.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "AlarmTimer.h"
#interface Assignment_1ViewController : UIViewController {
UIButton *SetButton;
UIDatePicker *Picker;
UIButton *CancelButton;
NSString *time;
AlarmTimer *alarmTimer;
}
#property (nonatomic, retain) IBOutlet UIButton *SetButton;
#property (nonatomic, retain) IBOutlet UIDatePicker *Picker;
#property (nonatomic, retain) IBOutlet UIButton *CancelButton;
- (IBAction)SetButtonPress:(id)sender;
- (IBAction)CancelButtonPress:(id)sender;
#end
.m:
//
// Assignment_1ViewController.m
// Assignment 1
//
// Created by Jack Schaible on 11-09-28.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "Assignment_1ViewController.h"
#import "AlarmTimer.h"
#implementation Assignment_1ViewController
#synthesize Picker;
#synthesize CancelButton;
#synthesize SetButton;
- (void)dealloc
{
[SetButton release];
[Picker release];
[CancelButton release];
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[self setSetButton:nil];
[self setPicker:nil];
[self setCancelButton:nil];
[super viewDidUnload];
[alarmTimer release];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (IBAction)SetButtonPress:(id)sender {
NSDateFormatter *ndf = [[NSDateFormatter alloc] init];
time = [ndf stringFromDate:self.Picker.date];
alarmTimer = [AlarmTimer alloc];
[alarmTimer init];
NSLog(#"Date is: %#", [ndf dateFromString:time]);
[alarmTimer RunTimer:[ndf dateFromString:time]];
}
- (IBAction)CancelButtonPress:(id)sender {
}
#end
And the code for the AlarmTimer class:
.h:
//
// Timer.h
// Assignment 1
//
// Created by Jack Schaible on 11-09-28.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#interface AlarmTimer : NSObject {
NSString *time;
NSTimer *timer;
AVAudioPlayer *player;
}
-(void) RunTimer: (NSDate *)date;
-(void)Play;
-(void)CancelTimer;
#end
And finally, the .m:
//
// Timer.m
// Assignment 1
//
// Created by Jack Schaible on 11-09-28.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "AlarmTimer.h"
#implementation AlarmTimer
-(id) init
{
if(self == [super init])
{
timer = [NSTimer alloc];
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/Time.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
}
return self;
}
- (void) RunTimer: (NSDate *)date
{
[timer initWithFireDate:date interval:86400 target:self selector:#selector(Play) userInfo:nil repeats:NO];
NSLog(#"Date is: %#", date);
}
-(void) Play
{
[player play];
UIAlertView *aView = [[UIAlertView alloc] initWithTitle:#"Holy Chimes!" message:#"If you didn't hear that..." delegate:self cancelButtonTitle:#"Okay" otherButtonTitles:nil, nil];
[aView show];
}
-(void)CancelTimer
{
time = nil;
}
#end
Thanks a lot to anyone who can help!
Why do you use NSDateFormatter? try to do it like that:
alarmTimer = [AlarmTimer alloc];
[alarmTimer init];
NSLog(#"Date is: %#", self.Picker.date);
[alarmTimer RunTimer: self.Picker.date];
As a rule, do the +alloc and -init together: Foo *someFoo = [[Foo alloc] init];. Don't separate the calls as you've done with your timer and alarmTimer variables. The reason for this is that sometimes the initializer will return a different pointer than what you get from +alloc, and when that happens you want to be certain to store that new pointer. I don't know if NSTimer ever does that, but if so you'll have all kinds of trouble with your current code.
Don't try to reuse a timer. Once it fires, release it and create a new one when you need to.
What's all that craziness in -SetButtonPress:? You get a date from a date picker, convert it to a string, and then immediately convert that string back to a date... why?
You're using an interval of 24 hours (86400 seconds) for your timer. Are you under the mistaken impression that a timer with a long interval will wake up an app that's in the background? NSTimer relies on the run loop; when the run loop isn't running, the timer isn't working.
It'd be somewhat easier to help you if you'd stick to the usual Objective-C convention and name methods starting with lower-case characters, i.e. -runTimer: instead of -RunTimer:. The same rule applies to instance variables.