I have a couple of views, one being the SettingsMenu the other being Game .
I have initialised backgroundMusic from within Game. When there is a change in SettingsMenu, I'd like it to run backgroundMusicStop in settingsMenu, however backgroundMusicStop is part of my Game class.
Game.h
-(void)backgroundMusicStop;
Game.m
-(void)backgroundMusicStop {
[backgroundMusic stop];
backgroundMusic.currentTime = 0;
}
SettingsMenu.m
-(IBAction)musicOptionSwitch:(id)sender {
if (backgroundMusicPlay == YES) {
backgroundMusicPlay = NO;
[Game backgroundMusicStop];
}
}
I've looked into it and I don't understand how to fix it, I know I need to make the method accessible from all classes, but I am confused on how to do that, any help would be greatly appreciated.
The problem is in this line of code:
[Game backgroundMusicStop];
In your implementation, Game is a class name and you try to call an instance method on a class. There are 2 options to fix it:
Create a new Game instance
Make backgroundMusicStop a class method
To create a Game instance you would need something like:
Game myGame = [[Game alloc]init];
//depending on your implementation it could be different
Or if you choose for second option you will have to change
-(void)backgroundMusicStop;
to
+(void)backgroundMusicStop;
I would also suggest you to find some book/website to understand class and instance variables/methods better and more in-depth.
I think the best architecture for what you're trying to do would be:
create one instance of a Settings object and have it keep track of the
current state of each option you want the user to control
update those values from inside your SettingsMenu
have your Game listen for changes to the Settings values
The advantage in this is that your view controllers only communicate through changes to the data model (Settings) rather than having to know about each other. Your Settings object can either be a singleton or a normal object that you get from your app delegate.
First create the settings data model:
// Settings.h
#import <Foundation/Foundation.h>
#interface Settings : NSObject
// A singleton so that the example stays simple
+ (instancetype)sharedSettings;
// YES/NO. Add other properties as needed.
#property (nonatomic, assign) BOOL musicShouldPlay;
#define MUSIC_SHOULD_PLAY #"musicShouldPlay"
#end
// Settings.m
#import "Settings.h"
#implementation Settings
// Guarantee only one instance
+ (instancetype)sharedSettings {
static dispatch_once_t onceToken;
static Settings *result = nil;
dispatch_once(&onceToken, ^{
result = [[Settings alloc] init];
});
return result;
}
- (instancetype)init {
self = [super init];
if (self) {
_musicShouldPlay = NO;
}
return self;
}
#end
The controller that makes the changes:
// SettingsViewController.m
#import "SettingsViewController.h"
#import "Settings.h"
#interface SettingsViewController ()
#property (strong, nonatomic) IBOutlet UISwitch *musicSwitch;
#end
#implementation SettingsViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Make the display consistent with previous settings
[self.musicSwitch setOn:[[Settings sharedSettings] musicShouldPlay]];
}
// I'm presenting this using a modal segue from a "Change settings" button on the main controller,
// so it has a "Done" button
- (IBAction)done:(id)sender {
[ self dismissViewControllerAnimated:YES completion:nil];
}
// Switch value changes are connected to this in the storyboard
- (IBAction)changePlayState:(id)sender {
[[Settings sharedSettings] setMusicShouldPlay:[self.musicSwitch isOn]];
}
#end
The controller that reacts to changes:
// ViewController.m
#import "ViewController.h"
#import "Settings.h"
#implementation ViewController
// Since this is the primary controller, I'll have it listen for its lifetime.
- (void)viewDidLoad {
[super viewDidLoad];
[[Settings sharedSettings] addObserver:self
forKeyPath:MUSIC_SHOULD_PLAY
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)dealloc {
[[Settings sharedSettings] removeObserver:self forKeyPath:MUSIC_SHOULD_PLAY];
}
// Here's where the KVO notifications are delivered.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:MUSIC_SHOULD_PLAY]) {
NSNumber *newValue = [change valueForKey:NSKeyValueChangeNewKey];
if ([newValue integerValue] == 1) {
NSLog(#"Request to turn music on");
} else {
NSLog(#"Request to turn music off");
}
}
}
#end
Related
I am trying to update the contents of an NSTextView that is connected to myViewController as a referencing outlet to the Files Owner which is the subclass myViewController.
When I use an IBAction from a button, or use the viewDidLoad method of the controller, I can update the text fine. However, when I try run the method from another class (referred to in this example as anotherViewController), it runs the method, but the textview does not change.
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
self.outText.string = #"I am updated text! I also work!";
}
- (void)updateTextView:(NSString *)argText {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
- (void)updateTextViewWithoutArg {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
#end
In anotherViewController.m , which has all the relevant imports, I call this:
myViewController *viewtask = [[myViewController alloc] init];
[viewtask updateTextViewWithoutArg];
Nothing happens. The method runs and logs that it should have updated, but no text updates. I have tried many different approaches, including textstorage and scrollrange methods, they all work the already working sections, but make no difference in the sections not working.
I've also tried just for fun:
myViewController *viewtask;
[viewtask updateTextViewWithoutArg];
Also using the instance variable _outText
Also using [self.outText setString:#"string"];
Also using [_outText setString:#"string"];
Again, they work but only in the already working sections.
This should be simple but isn't logical to me. In swift all I need to do is
self.outText.string = "I update whenever I'm called!"
Views you create in Interface Builder are lazily created, so if you access them before viewDidLoad is called they are nil.
If your case, calling
myViewController *viewtask = [[myViewController alloc] init];
does not cause the views to be created so when you call
[viewtask updateTextViewWithoutArg];
self.outText is nil.
You can see that this is what is happening by updating your code as below:
- (void)updateTextView:(NSString *)argText {
NSAssert(self.outText != nil, #"self.outText must not be nil");
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
you should see the assert fire.
I appear to have found a solution by making myViewController a singleton class and using sharedInstance. For this particlar app, myViewController is a debug output window and will never need to be placed in another view.
I won't accept this answer yet, as it's not the best one I'm sure. There may still be a proper solution presented that allows finding the applicable myViewController instance, and modifying the outText property attached to it. Using this singleton makes subclassing tedious as I would have to make a new class for every instance if I wanted to be able to address say 10 View Controllers.
Anyway - the way I've been able to satisfy my simple requirement:
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
+ (id)sharedInstance;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
static myViewController *sharedInstance = nil;
+ (myViewController *)sharedInstance {
static myViewController *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[myViewController alloc] init];
});
return sharedInstance;
}
- (void)viewDidLoad {
[super viewDidLoad];
sharedInstance = self;
}
- (void)viewDidUnload {
sharedInstance = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
sharedInstance.outText.string = #"Button Pressed";
}
- (void)updateTextView:(NSString *)argText {
sharedInstance.outText.string = argText;
}
- (void)updateTextViewWithoutArg {
sharedInstance.outText.string = #"I make it to the TextView now";
}
#end
Now when I use this code from within anotherViewController.m it updates the right instance:
[myViewController.sharedInstance updateTextView:#"Updating with this string"];
I have successfully implemented a file-drop functionality in my app. The Application window has a few NSTabView objects where dropping on them does not work. Anywhere else in the window the file-drop works fine.
I have tried to make the app delegate a delegate for the NSTabView, but this did not help.
Anyone have a setup for the NSTabView not to filter out the drop-actions so the whole window can be transparent to the file-drop actions ?
For a more generic solution than olekeh's I made it IB friendly so you can hook it up to any object that complies with the NSDraggingDestination protocol.
#import <Cocoa/Cocoa.h>
#interface DropFilesView : NSView
#property (nullable, assign) IBOutlet id<NSDraggingDestination> dropDelegate;
#end
#implementation DropFilesView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
-(void) awakeFromNib {
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType,
(NSString *)kPasteboardTypeFileURLPromise,kUTTypeData, NSURLPboardType, nil]]; //kUTTypeData
[super awakeFromNib];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender{
return [self.dropDelegate draggingEntered:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
return [self.dropDelegate performDragOperation:sender];
}
#end
I found the solution to this !! - I am posting it here for others who might need.
The NSTabView object has for each of its tabs an NSTabViwItem.
Under each of those, there is a regular NSView - that I subclassed with the following code: - The code assumes that you already have "draggingEntered" and "performDragOperation" in your AppDelegate as this class just forwards these messages to the app delegate. You will also need to put the declarations for those methods in you AppDelegate.h
// DropFilesView.h
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#interface DropFilesView : NSView
#end
and the implementation:
// DropFilesView.m
#import "DropFilesView.h"
#implementation DropFilesView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
-(void) awakeFromNib {
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType,
(NSString *)kPasteboardTypeFileURLPromise,kUTTypeData, NSURLPboardType, nil]]; //kUTTypeData
[super awakeFromNib];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
AppDelegate* del = [AppDelegate sharedAppDelegate];
return [del draggingEntered:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
AppDelegate* del = [AppDelegate sharedAppDelegate];
return [del performDragOperation:sender];
}
#end
In Interfacebuilder, I set the new class for all the NSView objects covering areas where drop does not work, to this new one.
A similar approach can be used for NSImageView and the WebView classes. However, for the last one, do not use [super awakeFromNib] to prevent the default drag-and drop handling for the web view object.
I wanna setup a basic PFQuery Table View Controller. I wanna display the usernames from my User class. I've added the User class to query on, but it gives this error message: *Property 'className' not found on object of type 'InboxViewController '
I don't understand why happens this, because the user class exists, it appears in the data browser. I've tried to make a property in the .h file, but it was unsuccessful.
#import "InboxViewController.h"
#import <Parse/Parse.h>
#import "LoginViewController.m"
#interface InboxViewController ()
#end
#implementation InboxViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom the table
// The className to query on
self.className = #"User";
// The key of the PFObject to display in the label of the default cell style
self.textKey = #"username";
// Uncomment the following line to specify the key of a PFFile on the PFObject to display in the imageView of the default cell style
// self.imageKey = #"image";
// Whether the built-in pull-to-refresh is enabled
self.pullToRefreshEnabled = YES;
// Whether the built-in pagination is enabled
self.paginationEnabled = YES;
// The number of objects to show per page
self.objectsPerPage = 25;
}
return self;
}
#pragma mark - UIViewController
- (void)viewDidLoad
{
[super viewDidLoad];
PFUser *currentUser = [PFUser currentUser];
if (currentUser) {
NSLog(#"Current user: %#", currentUser.username);
} else {
[self performSegueWithIdentifier:#"showLogin" sender:self];
}
}
.h file
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#interface InboxViewController : PFQueryTableViewController
- (IBAction)logout:(id)sender;
#end
OK, I just remembered.
The property you are looking for is parseClassName not className.
Change this and it should work.
I'm trying to set a launch image to my application, however, it will not work the right way no matter what I do.
My app is landscape only(left or right), but I though I would just put a portrait launch image anyways.
The launch image only shows up if I check the "portrait" box and uncheck the "Landscape left" and "Landscape right" boxes in the "Deployment Info" settings. Obviously I can't do that because it will mess up my whole app.
I tried changing shouldAutorotateToInterfaceOrientation: to return YES in all of my view controllers, but that didn't work.
Any help is appreciated. Thanks!
-Xerif
I was having the same exact probably as your having and unfortunately i have found out that this is a glitch/bug in the Xcode software. I have come up with code that can display a launch image in landscape mode without any size requirements! Try to keep up with me and message me if you have any problems. Here are the steps:
Create a new objective-c file with a subclass of NSObject. Name it GameData.
inside GameData.h enter this code:
#import <Foundation/Foundation.h>
#interface GameData : NSObject
#property (assign, nonatomic) int Mainint;
+(instancetype)sharedGameData;
#end
Now inside of GameData.m enter this code:
#import "GameData.h"
#implementation GameData
+ (instancetype)sharedGameData
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
-(void)launchImage
{
[GameData sharedGameData].Mainint = [[NSUserDefaults standardUserDefaults] integerForKey:#"mainint"];
[[NSUserDefaults standardUserDefaults] setInteger:0 forKey:#"mainint"];
}
Now go into your AppDelegate.h file and enter this:
#import "GameData.h"
Now go into your AppDelegate.m file and enter this in the below method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
[[NSUserDefaults standardUserDefaults] setInteger:0 forKey:#"mainint"]; //Add This
// Override point for customization after application launch.
return YES;
}
Now go into your storyboard and drag a UILabel onto your initial View Controller or the View controller that shows first when app is launched. Don't worry we will hide this label so you will not see it.
Now drag a UIImageView across the entire screen of your initial View Controller
Now go into your initial View Controllers .h file (mine is called ViewController.h) and add the following:
#import <UIKit/UIKit.h>
#import "GameData.h" //Add this
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIImageView *launchImage; //Add this
#property IBOutlet UILabel *seconds; //Add this
#property NSTimer *timer; //Add this
#end
Now go into your initial view controller .m file (mine is ViewController.m) and add the following:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize launchImage;
#synthesize timer;
#synthesize seconds;
- (void)viewDidLoad
{
[GameData sharedGameData].Mainint = [[NSUserDefaults standardUserDefaults] integerForKey:#"mainint"];
seconds.hidden = YES;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countUp) userInfo:nil repeats:YES];
if ([GameData sharedGameData].Mainint > 3) {
launchImage.hidden = YES;
}
[self countUp];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)countUp
{
[GameData sharedGameData].Mainint += 1;
seconds.text = [NSString stringWithFormat:#"%i", [GameData sharedGameData].Mainint];
if ([GameData sharedGameData].Mainint == 3) {
launchImage.hidden = YES;
[GameData sharedGameData].Mainint = 4;
[[NSUserDefaults standardUserDefaults] setInteger:[GameData sharedGameData].Mainint forKey:#"mainint"];
}
}
If you follow those steps exactly it should work and you should have no problems. If something does not work just let me know i'd be happy to help. I searched for days and days trying to find the answer and then finally figured it out through testing this code. Good Luck!
I did a lot of research on UISplitView and was not able to find a way to control a Split View when the Master and the Detail has a view that changes.
Then I found a way to manage it with a singleton class that is the delegate.
My problem is that i'm not sure if it's the right way to go. I'm concerned about reusability and memory managment. Also I have a feeling that it's aginst Apple guidelines to make delegates in singletons.
This is what I have (and it's actually working):
// SharedSplitViewDelegate.h
/* In the detail view controllers:
// in the initial detail view controller
- (void)awakeFromNib
{
[super awakeFromNib];
// needs to be here, otherwise if it's booted in portrait the button is not set
self.splitViewController.delegate = [SharedSplitViewDelegate initSharedSplitViewDelegate];
}
// shared between all detail view controllers
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
SharedSplitViewDelegate *rotationHandler = [SharedSplitViewDelegate initSharedSplitViewDelegate];
[self.toolbar setItems:[rotationHandler processButtonArray:self.toolbar.items] animated:YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface SharedSplitViewDelegate : NSObject <UISplitViewControllerDelegate>
+ (id)initSharedSplitViewDelegate; // returns the singleton class instance
- (NSArray *)processButtonArray:(NSArray *)array; // Adds and removes the button from the toolbar array. Returns the modified array.
#end
Now the implementation:
// SharedSplitViewDelegate.m
#import "SharedSplitViewDelegate.h"
#interface SharedSplitViewDelegate()
#property (nonatomic, strong) UIBarButtonItem *button;
#property (nonatomic, strong) UIBarButtonItem *cachedButton;
#end
#implementation SharedSplitViewDelegate
#synthesize button = _button;
#synthesize cachedButton = _cachedButton;
#pragma mark - Singleton class definition
static id sharedSplitViewDelegate = nil;
+ (void)initialize
{
if (self == [SharedSplitViewDelegate class]) {
sharedSplitViewDelegate = [[self alloc] init];
}
}
+ (id)initSharedSplitViewDelegate {
return sharedSplitViewDelegate;
}
#pragma mark - Split view delegate methods
- (BOOL)splitViewController:(UISplitViewController *)svc
shouldHideViewController:(UIViewController *)vc
inOrientation:(UIInterfaceOrientation)orientation
{
if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
return NO;
} else {
return YES;
}
}
- (void)splitViewController:(UISplitViewController *)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem *)barButtonItem
forPopoverController:(UIPopoverController *)pc
{
barButtonItem.title = #"Browse";
self.button = barButtonItem;
}
- (void)splitViewController:(UISplitViewController *)svc
willShowViewController:(UIViewController *)aViewController
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
self.button = nil;
}
#pragma mark - Utility methods
- (void)setButton:(UIBarButtonItem *)button
{
if (button != _button) {
_button = button;
}
if (button != nil) {
self.cachedButton = button;
}
}
- (NSArray *)processButtonArray:(NSArray *)array
{
NSMutableArray *processedArray = [array mutableCopy];
if (self.button != nil && ![processedArray containsObject:self.button]) {
[processedArray insertObject:self.button atIndex:0];
} else if (self.button == nil && [processedArray containsObject:self.cachedButton]) {
[processedArray removeObjectAtIndex:0];
}
return [processedArray copy];
}
#end
This code is free to use and modify for everyone that would find it viable in their project :).
I'm new to StackOverflow (even though I've lurked for a couple months without an account) so every critique is warmly welcomed.
IMHO, every design pattern, architecture, is 'good' if it fits the 'problem' you have to solve (and fits your personal preferences for code organisation)
What's your problem ?
Why do you need this object ?
Could this singleton UISplitViewDelegate be your
UIApplicationDelegate ? (Keep it Simple ;-)
further discussion =>
If you UIApplicationDelegate is a mess, rather than creating sub-object, a scheme I've been using recently to organize my code : use categories and class extensions
Example :
If my ViewController class handles complex tasks whose code can be separated in groups.
let's say :
sound
core data
location-aware,
I create a category for each of these
UIViewController+soundManager
UIViewController+dataProvider
UIViewController+locationManager.
(in same file with several #interface #implementation, or in different files => i use several files)
Then along with each category I write a class-extension for properties this particular category needs.
Last time I solved this by subclassing the UISplitViewController and used it as his own delegate.