cannot display value passed by MainWindow on NSWindow? - nswindow

Here's my problem:
I have a MainWindow.xib, Window1.xib, Window1Controller.h, Window1Controller.m
I can display Window1 from MainWindow, but the passing value does not display on Window1, I know that value is ready to display, I create a button on Window1 and I can invoke this value. How can I display passing value after loaded Window1.
Here's my code:
AppDelegate.m
- (IBAction)openWindow1:(id)sender {
Window1Controller *w1 = [[Window1Controller alloc]initWithWindowNibName:#"Window1"];
[w1 showWindow:nil];
}
Window1Controller.m
- (void)displayInfo {
[label setStringValue:#"sample passed text"];
NSLog(#"%#",[label stringValue]);
}
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
[self displayInfo];
}

Did you connect the outlet of the label to your window controller? (and is your window controller the files owner of your window1.xib?)

Related

Configuring a UIViewController outside scene(_:willConnectTo:options:) then setting it to window rootViewController doesn't work

My intention is to initialize a view controller and set it to the window rootViewController, then pass the view controller as dependency to an AppCoordinator where the app decide what to do with the view controller (replace it with an onboarding scene or login page, or even home screen if the user is already logged in.
The problem is that whenever I do that, the window doesn't seems to get back its view controller, but instead a black screen.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
var viewController = UIViewController()
self.window?.rootViewController = viewController
let appCoordinator = AppCoordinator(viewController: viewController)
appCoordinator.start()
window?.makeKeyAndVisible()
}
AppCoordinator.swift
import UIKit
import SwiftUI
class AppCoordinator: Coordinator {
var viewController: UIViewController
init(viewController: UIViewController) {
self.viewController = viewController
}
func start() {
let onboardingView = OnboardingView()
self.viewController = UIHostingController(rootView: onboardingView)
}
}
If I bypass the AppCoordinator and set everything in the scene delegate, the screen loads correctly:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
var viewController = UIViewController()
let onboardingView = OnboardingView()
self.window?.rootViewController = UIHostingController(rootView: onboardingView)
window?.makeKeyAndVisible()
}
What seems to be wrong when I pass the view controller as a dependency? Btw, I don't prefer to pass the window as dependency to the called coordinator, although this works, I don't prefer it.

QML: connect slot to several controls

I would like to get notification in main Qt app in case any control in QML (loaded via QQuickWidget) changes its value. There are CheckBox'es, ComboBox'es, SpinBox'es and TextEditor's.
My current approach is to declare a slot for every control (onCheckedChanged, onCurrentIndexChanged, onValueChanged and onTextChanged respectively) and call mainApp.notifyMe() from them, there mainApp is a link to parent Qt Widget propagated to QML with help of
QQmlEngine* engine = new QQmlEngine(this);
if (QQmlContext* cntx = engine->rootContext()) {
cntx->setContextProperty("mainApp", this);
}
and notifyMe() is a slot in it on C++ side.
But this requires dozens of functions with the same code bcs I have dozens of controls. It would be ideal to have one or 4 functions with notifyMe() in QML which could be connected to all controls value changes.
Is there a way in QML to create such slot and connect it to multiple objects property changes?
I've end up with following in my root item:
MySettingsForm {
signal notify()
onNotify: {
mainApp.notifyMe();
}
Component.onCompleted: {
var cbs = [Combobox1, Combobox2, Combobox3];
for (var i in cbs) {
cbs[i].checkedChanged.connect(notify);
}
var sbs = [SpinBox1, SpinBox2, SpinBox3];
for (i in sbs) {
sbs[i].valueChanged.connect(notify);
}
var tes = [TextField1, TextFiel2, TextField3];
for (i in tes) {
tes[i].textChanged.connect(notify);
}
var cxs = [ComboBox1, ComboBox2, ComboBox3];
for (i in cxs) {
cxs[i].currentIndexChanged.connect(notify);
}
}
...
}
The trick is in creation of custom signal notify and slot attached to it.

Trying to get button to spin in WatchKit

the code i'm using works just fine in swift for iPhone apps but not in the WatchKit 7.0 beta. the outlets and actions are different. I'm not sure what needs to change to make it work in WatchKit. please help!
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
#IBOutlet var spinButton: WKInterfaceButton!
var isRotating = false
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
#IBAction func spinAction() {
if !isRotating {
// create a spin animation
let spinAnimation = CABasicAnimation()
// starts from 0
spinAnimation.fromValue = 0
// goes to 360 ( 2 * π )
spinAnimation.toValue = M_PI*2
// define how long it will take to complete a 360
spinAnimation.duration = 1
// make it spin infinitely
spinAnimation.repeatCount = Float.infinity
// do not remove when completed
spinAnimation.removedOnCompletion = false
// specify the fill mode
spinAnimation.fillMode = kCAFillModeForwards
// and the animation acceleration
spinAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// add the animation to the button layer
spinButton.layer.addAnimation(spinAnimation, forKey: "transform.rotation.z")
} else {
// remove the animation
spinButton.layer.removeAllAnimations()
}
// toggle its state
isRotating = !isRotating
}
}
You are limited to a subset of all the APIs available on iOS when developing for the watchOS.
If you want to do basic animations try out a WKInterfacePicker and change images when the digital crown is moved.
IBOutlet WKInterfacePicker *myPicker;
- (void)willActivate {
[super willActivate];
WKPickerItem *item1 = [[WKPickerItem alloc] init];
item1.contentImage = [WKImage imageWithImageName:#"Unknown.png"];
WKPickerItem *item2 = [[WKPickerItem alloc] init];
item2.contentImage = [WKImage imageWithImageName:#"Unknown-2.png"];
[self.myPicker setItems:array];
}
When the value exceeds the array count start over from index 0.
- (IBAction)myPickerAction:(NSInteger)value {
if (value % 2 == 0) {
[self.myPicker setSelectedItemIndex:-1];
}
}
This will make the WKInterfacePicker change between your images when the digital crown is rotated.

Having a UINavigationController in the master view of a UISplitViewController in iOS 8

In my UISplitViewController, the master view is a UINavigationController containing a UITableViewController. Sometime, when the user selects an item in the table, I have to push another UITableViewController over the existing table in the master view.
In iOS 7, inside my first UITableViewController I just call
[self.navigationController pushViewController:otherTableVC animated:YES];
In iOS 8:
When the split view is collapsed, the otherTableVC becomes the detail View!
Then after rotating the device, we see the two tables side by side...
Worse, if the device shows the two panes, the code works great and the second table is pushed over the first one in the master view. But, after a double rotation, the two tables are again side by side. It seems the collapsed mode of the UISplitViewController interferes with my own navigation controller…
How can I manage my own UINavigationController in the Master View?
Thank you
EDITED:
My both primary and details views have a navigation Controller. And to solve my problem, I just discovered that, in collapsed mode, I have to create an extra Navigation Controller and push it over the primary navigation controller.
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:otherTableVC];
[self.navigationController pushViewController:navController animated:YES];
So I just discovered hat we can push a navigation Controller inside another Navigation Controller.
Short answer, you can control this behaviour via the UISplitViewControllerDelegate methods:
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
splitViewController:separateSecondaryViewControllerFromPrimaryViewController:
I suspect what you really want to do is deal with the situation where you have an iOS 8 UISplitViewController-based app where your primary and detailed views are both UINavigationControllers and there are some viewControllers (within these navigation controllers) that you want to appear only on the primary or detail side of the split view. The answer below deals with this. It also copes with the situation where you sometimes wish for a view to replace the views in the Detail navigation controller, rather than getting pushed there.
A small caveat: the code below does not deal with all possible cases and has some assumptions:
We don't expect anything can change on the Detailed navigation controller stack when the split view is collapsed and those views are obscured by a detailed view above them.
Our UIViewController subclasses all have a shouldDisplayInDetailedView and shouldReplaceDetailedView property
We assume that we only push views onto the detailed navigation controller that have the shouldDisplayInDetailedView property set.
View controllers are added to the Detail side via splitViewController:showDetailViewController: or a pushViewController:animated: on the navigationController property of a view within a detailed view (in either expanded or collapsed state).
View controllers that should replace the view controllers in the Detail navigation controller are only added via splitViewController:showDetailViewController: and only from interaction with view in the Primary view controller, i.e., this can only happen if the Primary view controller is not obscured when in collapsed state.
We have a BlankViewController to display in the Detail View when the split view controller gets expanded but we only have view controllers that should stay on the Primary side.
I don't recommend implementing only one side of the splitViewController:collapseSecondaryViewController:ontoPrimaryViewController: / splitViewController: separateSecondaryViewControllerFromPrimaryViewController: logic and depending on the default implementation for the other side. Apple do some strange things like putting the UINavigationViewController from the Detail side into the primary side as one of the viewControllers in the Primary navigation controller stack but then pushing other view controllers above it, which even if you understand completely still can't be replicated from your own code. Thus its best to handle both sides of the process yourself.
This is what I use:
#pragma mark -
#pragma mark Split View Controller delegate.
- (BOOL)splitViewController:(UISplitViewController *)splitViewController showViewController:(UIViewController *)vc sender:(id)sender
{
//Standard behaviour. This won't get called in our case when the split view is collapsed and the primary view controllers are obscured.
return NO;
}
// Since we treat warnings as errors, silence warning about unknown selector below on UIViewController subclasses.
#pragma GCC diagnostic ignored "-Wundeclared-selector"
- (BOOL)splitViewController:(UISplitViewController *)splitViewController showDetailViewController:(UIViewController *)vc sender:(id)sender
{
if (splitViewController.collapsed == NO)
{
// The navigation controller we'll be adding the view controller vc to.
UINavigationController *navController = splitViewController.viewControllers[1];
UIViewController *topDetailViewController = [navController.viewControllers lastObject];
if ([topDetailViewController isKindOfClass:[BlankViewController class]] ||
([vc respondsToSelector:#selector(shouldReplaceDetailedView)] && [vc performSelector:#selector(shouldReplaceDetailedView)]))
{
// Replace the (expanded) detail view with this new view controller.
[navController setViewControllers:#[vc] animated:NO];
}
else
{
// Otherwise, just push.
[navController pushViewController:vc animated:YES];
}
}
else
{
// Collapsed. Just push onto the conbined primary and detailed navigation controller.
UINavigationController *navController = splitViewController.viewControllers[0];
[navController pushViewController:vc animated:YES];
}
// We've handled this ourselves.
return YES;
}
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController
{
UINavigationController *primaryNavController = (UINavigationController *)primaryViewController;
UINavigationController *secondaryNavController = (UINavigationController *)secondaryViewController;
UIViewController *bottomSecondaryView = [secondaryNavController.viewControllers firstObject];
if ([bottomSecondaryView isKindOfClass:[BlankViewController class]])
{
NSAssert([secondaryNavController.viewControllers count] == 1, #"BlankViewController is not only detail view controller");
// If our secondary controller is blank, do the collapse ourself by doing nothing.
return YES;
}
// We need to shift these view controllers ourselves.
// This should be the primary views and then the detailed views on top.
// Otherwise the UISplitViewController does wacky things like embedding a UINavigationController inside another UINavigation Controller, which causes problems for us later.
NSMutableArray *newPrimaryViewControllers = [NSMutableArray arrayWithArray:primaryNavController.viewControllers];
[newPrimaryViewControllers addObjectsFromArray:secondaryNavController.viewControllers];
primaryNavController.viewControllers = newPrimaryViewControllers;
return YES;
}
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController
{
UINavigationController *primaryNavController = (UINavigationController *)primaryViewController;
// Split up the combined primary and detail navigation controller in their component primary and detail view controller lists, but with same ordering.
NSMutableArray *newPrimaryViewControllers = [NSMutableArray array];
NSMutableArray *newDetailViewControllers = [NSMutableArray array];
for (UIViewController *controller in primaryNavController.viewControllers)
{
if ([controller respondsToSelector:#selector(shouldDisplayInDetailedView)] && [controller performSelector:#selector(shouldDisplayInDetailedView)])
{
[newDetailViewControllers addObject:controller];
}
else
{
[newPrimaryViewControllers addObject:controller];
}
}
if (newDetailViewControllers.count == 0)
{
// If there's no detailed views on the top of the navigation stack, return a blank view (in navigation controller) for detailed side.
UINavigationController *blankDetailNavController = [[UINavigationController alloc] initWithRootViewController:[[BlankViewController alloc] init]];
return blankDetailNavController;
}
// Set the new primary views.
primaryNavController.viewControllers = newPrimaryViewControllers;
// Return the new detail navigation controller and views.
UINavigationController *detailNavController = [[UINavigationController alloc] init];
detailNavController.viewControllers = newDetailViewControllers;
return detailNavController;
}
Swift 4 Version with minor changes to make it work with my code:
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if !isCollapsed {
// in expanded mode set new VC as top view controller of the detail nav controller
if let detailNavigationController = viewControllers[1] as? UINavigationController {
detailNavigationController.setViewControllers([vc], animated: false)
}
} else {
// in collapsed mode push the new view controller on the master nav controller
if let masterNavigationController = viewControllers[0] as? UINavigationController {
masterNavigationController.pushViewController(vc, animated: true)
}
}
return true
}
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
let masterNavigationController = primaryViewController as? UINavigationController
let detailNavigationController = secondaryViewController as? UINavigationController
let episodeDetailViewController = detailNavigationController?.viewControllers.first as? EpisodeDetailTableViewController
if episodeDetailViewController?.episode == nil {
// detail view is blank. We do not need to push this onto the master
return true
}
guard var newMasterViewControllers = masterNavigationController?.viewControllers else { return false }
newMasterViewControllers.append(contentsOf: detailNavigationController?.viewControllers ?? [])
masterNavigationController?.setViewControllers(newMasterViewControllers, animated: false)
return true
}
func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? {
let masterNavigationViewController = primaryViewController as? UINavigationController
var newMasterViewControllers = [UIViewController]()
var newDetailViewControllers = [UIViewController]()
for vc in masterNavigationViewController?.viewControllers ?? [] {
if vc is PodcastsTableViewController || vc is EpisodesTableViewController {
newMasterViewControllers.append(vc)
} else {
newDetailViewControllers.append(vc)
}
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let detailNavigationController = storyboard.instantiateViewController(withIdentifier: "splitViewDetailViewController") as! UINavigationController
if newDetailViewControllers.count == 0 {
let emptyEpisodeDetailViewController = storyboard.instantiateViewController(withIdentifier: "episodeDetail")
newDetailViewControllers.append(emptyEpisodeDetailViewController)
}
masterNavigationViewController?.setViewControllers(newMasterViewControllers, animated: false)
detailNavigationController.setViewControllers(newDetailViewControllers, animated: false)
return detailNavigationController
}

Cocos2d: Is it ok to have a pointer to a parent's class pointer?

Silly question. Cocos2d is build around the parent-child hierarchy. I was wondering if it is ok to have a parent class (e.g. GameScene) and initialize a child class (e.g. SpriteHandler) with a pointer to a member of the parent class (e.g. CCSpriteBatchNode*).
I am trying to do this to optimize the number of CCSpriteBatchNodes. Here is a code snippet of my main class (GameScene style).
#import <Foundation/Foundation.h>
#import "cocos2d.h"
enum ShooterSceneLayerTags {
HudLayerTag = 0,
};
#interface ShooterScene : CCLayer {
CCSpriteBatchNode* sharedSpriteBatchNode;
}
-(id) initWithSharedBatchNodeReference:( CCSpriteBatchNode*) sharedSpriteBatchNode;
+ (id) sceneWithId:(int)sceneId;
#end
#import "ShooterScene.h"
#import "MainMenuScene.h"
//Layers
#import "LevelSpritesLayer.h"
#import "HudLayer.h"
#interface ShooterScene (PrivateMethods)
-(void) addLayers:(int)sceneId;
-(void) loadGameArtFile;
-(BOOL) verifyAndHandlePause;
#end
#implementation ShooterScene
+ (id) sceneWithId:(int)sceneId
{
CCScene *scene = [CCScene node];
ShooterScene * shooterLayer = [[self alloc] initWithId:sceneId];
[scene addChild:shooterLayer];
return scene;
}
-(id) initWithId:(int)sceneId
{
if ((self = [super init]))
{
//Load game art before adding layers - This will initialize the batch node
[self loadGameArtFile:sceneId];
//Will add sprites to shared batch node for performance
[self addLayers:sceneId];
[self addChild:sharedSpriteBatchNode];
//Do other stuff..
[self scheduleUpdate];
}
return self;
}
-(void) addLayers:(int)sceneId
{
LevelSpritesLayer * levelData = [LevelSpritesLayer node];
[levelData initWithSharedBatchNodeReference:sharedSpriteBatchNode];
[self addChild:levelData];
switch (sceneId) {
case 1:
[levelData loadLevelOneSprites];
break;
case 2:
[levelData loadLevelTwoSprites];
break;
default:
break;
}
HudLayer * hud = [HudLayer node];
[hud setUpPauseMenu];
[self addChild:hud z:1 tag:HudLayerTag];
}
-(BOOL) verifyAndHandlePause
{
HudLayer * hud = [self getChildByTag:HudLayerTag];
if(hud.pauseRequested){
[[CCDirector sharedDirector] replaceScene:[MainMenuScene scene]];
return true;
}
else {
return false;
}
}
-(void) update:(ccTime)delta
{
if([self verifyAndHandlePause]==false)
{
//Continue with animation etc..
}
}
/**
This is tricky. Could have loaded this in LevelData but as I am expecting to use the same SpriteSheet for HudLayer as well then
I prefer to have the control here of this. Also, the same sheet could be used for more level, hence specific function is not bad
**/
-(void) loadGameArtFile:(int) sceneId
{
CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];
[frameCache addSpriteFramesWithFile:#"game-art-hd.plist"];
sharedSpriteBatchNode = [CCSpriteBatchNode batchNodeWithFile:#"game-art-hd.png"];
}
//As dealloc is deprecated, I prefer to remove unused sprites and texture on cleanup
-(void) cleanup
{
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
}
And here is one of the loadLevelData methods, it uses the sharedSpriteBAtchNodeReference
-(void) loadLevelOneSprites
{
//Just a proof of concept example
testSprite = [CCSprite spriteWithSpriteFrameName:#"File0.png"];
testSprite.anchorPoint = CGPointMake(0.5f, 0.5f);
testSprite.position = CGPointMake(160.0f, 240.0f);
[sharedSpriteBatchNodeReference addChild:testSprite];
}
You can do that but if you are using ARC you should make your sharedSpriteBatchNode a weak pointer. If you don't you could end up with a circular reference.
What will happen with the circular reference is when the Director releases your game scene after it has finished running your game scene will still have your child retaining it and your game scene will still be retaining that child. This circle will float off and never be able to be released as it is abandoned.
What [Ben] said. It should not be a retaining or strong reference, the latter being the default in ARC for instance variables.
There is one way to ensure under ARC that you're retain-cycle-safe even if you use a strong reference. Override the cleanup method and nil the reference there (under MRC you should also call release here, if you retained the reference):
-(void) cleanup
{
sharedSpriteBatchNode = nil;
[super cleanup];
}
Doing this in dealloc won't work. As long as a child node has a strong reference to a parent, it will not be deallocated. So you need to do this in cleanup, and ensure that all method calls where you can set the cleanup flag has that parameter set to YES.
But there are other, and I think better, solutions than passing a parent node in the initializer. For example you could get the shared batch node via a common tag from the parent, and either do that every time you need it (wrap it into a small function) or store it in a weak (non-retaining) instance var:
// onEnter is typically called right after init (during addChild)
// parent is already set here
-(void) onEnter
{
[super onEnter];
CCSpriteBatchNode* sharedBatchNode = [parent getChildByTag:kSharedBatchNodeTag];
}
Or get the parent and cast it, assuming the sharedBatchNode to be a property of the parent class:
-(void) whereEver
{
ShooterScene* scene = (ShooterScene*)parent;
CCSpriteBatchNode* sharedBatchNode = scene.sharedSpriteBatchNode;
…
// you can also reduce the above to a single line:
CCSpriteBatchNode* batch = ((ShooterScene*)parent).sharedSpriteBatchNode;
}
Especially this latter solution is recommended. Even if you need to do that often it's fast. Casting is free, property access no more than a message send. Just be sure that the parent is in fact an object of the class you're casting it to.

Resources