Different views depending on orientation on iOS - xcode

I know this question has been asked before, but how can I support a different view depending on my interface orientation in iOS 5?
An example by Apple shows a PortraitViewController and a LandscapeViewController. The PortraitViewController creates the LandscapeViewController and registers itself for rotation notifications. When it receives a rotation notification it either pushes or pops the LandscapeViewController depending on current interface orientation.
So far so good.
But how can I keep data both controllers have synchronized? (They display the same data obviously, since they are technically the same controller to the user, just with different views in landscape and portrait). Both have a "reload data button" which reloads their data. How can I tell the PortraitViewController to show the same data when the LandscapeViewController reloads it and vice versa?
Another problem is with memory warnings. When the interface is in landscape mode and I receive a memory warning my PortraitViewController gets unloaded. Now if I reload the data for my LandscapeViewController the PortraitViewController cannot do the same, because it is unloaded and has its Outlets still set to nil.
How do I adress all of these issues?

you actually first have to make new xib...
go to file - new - file
select user interface
select view
and so on to make new XIB
now select new view
go to identity inspector in right panel
add class name to connect the xib to old .h and .m files
important step...
connect new xib view with file's owner
now new xib is connected with the oldviewcontroller .h and .m
last step
in appdelegate use if statement to change the nibname based on IOS
for this you can use this..
[[UIDevice currentDevice] systemVersion]
in if statement you have to change initwithname with new view..
self.viewController = [[[ViewController alloc] initWithNibName:#"View" bundle:nil] autorelease

So what I ended up doing is using one ViewController with 2 views which shows a different view depending on interface orientation. The downside of this is that I have to connect all outlets twice and manipulate them twice.
But this way I can easily share my data and avoid one view being unloaded while the other isn't.

You should have a new class which will store your data. Both viewController will have a reference to this dataController (or whatever name you want).
You can also add a bit of logic on viewWillAppear and viewWillDisappear of your viewControllers.
Finally, for the memory warning issue, you can check on the viewWillAppear is the view is loaded and act accordingly.

Related

How to wire-connect NSTableView/Table View to avoid runtime error "Could not connect action, target class NSObject does not respond to")

In a view-based single-column NSTableView containing a default NSTextField in its Table Cell View, I'm trying to listen to confirmed user edits by connecting the NSTextField's action, within an Interface Builder view of the .xib, to a method in my ViewController for the window. But at run time (during window initialization) I get "Could not connect action, target class NSObject does not respond to -textCellChanged". I don't understand which NSObject is being incorrectly targeted, and I have many other NSViews in the window correctly connected to other outlets and actions in the same WindowController.
I see various other posts with a similar symptom, often also in the context of NSTableView, and have explored the solutions or partial solutions to those other problems in my context without success. Is there any particular magic about wiring Table View Cells in Interface Builder that I am overlooking? Or perhaps phrased differently: how is the target object actually determined at runtime, when the action is simply a class method in the File's Owner (and when does this vary for different controls all wired to the same owner of a common superview)?
Here are some particulars of context:
File Owner is set to a subclass of NSWindowController and Module there correctly inherited to my app target.
Probably relevant: I am not using Storyboards, and the top-level object in my XIB's outline view hierarchy is a Window (NSPanel), rather than an View or View Controller. The NSWindowController only appears within the XIB as the File Owner (not as its own object in Outline View).
In any of the various wiring scenarios I've tried (following), Interface Builder "looks like" the wiring op has succeeded. After wiring, the File Owner's Connections inspector lists the intended connection under Received Actions ("textCellChanged: ... [x] Table View Cell"), along with many other actions connecting components in the NSTableView's superview to other methods in the NSViewController.
Likewise, connecting the NSTextField as an OUTLET in the same NSViewController works with no problem. It's only the action (or target/action?? in IB one only sets "action") that fails.
The File Owner is also the data source and delegate of the NSTableView, and the NSTextField is set to Action:Send on End of Editing and Behavior:Editable. I don't think any of these are relevant to the particular symptom, which is just a failure to connect an action.
The NSWindowController is Swift; I have tried implementing the appropriate action within both the main NSWindowController implementation or in an extension that implements NSTableViewDelegate to no noticeably different effect.
Other posts suggest Xcode bugs in wiring, though in older versions of Xcode (I'm in 10.2). Here are various approaches I've tried, all with similar results:
Ctrl+Drag from Table View Cell icon in IB Outline View to NSWindowController source module, targeting there either an existing #IBAction or permitting Xcode to generate a new Connection (type Action, Object: File's Owner) and with it a new method in the ViewController
Reverse Drag from Source Code "left-column radio circle" next to an #IBAction to the Table View Cell in Outline View of my .xib
Ctrl+Drag from Table View Cell icon (in Outline View) to Placeholder/File's Owner icon, then choosing an appropriate action method from the pop-up list of methods implemented in the view controller.
Possibly some others
Finally, here are some related posts and how they differ:
This sounds like an identical symptom, but in comments OP claims to have fixed the issue by a combination of setting File Owner to the View Controller (done) and by working around blocking XCode bugs (not visible in my context).
This suggests I'm linking to a stale (deleted) method; definitely not the case when I allow Xcode to create the method for me.
This unanswered post suggests the user gave up on the situation as an IB bug and gave up in preference for a non-target/action workaround. I suppose I could pursue listening to notifications on the NSTextField as a similar workaround.
Finally, the accepted answer to a similar symptom here is that the connection to File Owner is incorrect in that case, where File Owner was the NSApplication object rather than the View Controller. In my case, File Owner is the View Controller object that defines these methods, so feels like the correct target.
Any stone unturned here? Thanks in advance for your help.
The file's owner in a xib file is a placeholder object representing the owner of the nib and representing the owner object passed to makeView(withIdentifier:owner:). Pass the owner of the nib (usually the view controller or window controller) to makeView(withIdentifier:owner:).

Why can't I connect my menu to my view controller IBAction?

I have a document based application. I have just created menu items in the storyboard and IBActions in my view controller. However the usual way I connect an action to a target doesn't work
-(IBAction) markAsHidden:(id)sender;
-(IBAction) markAsVisible:(id)sender;
-(IBAction) toggleHidden:(id)sender;
Here is what I see when from my menu item I press Ctrl and mouse click from menu to View Controller. It does not show my IBActions.
Any idea ? My 2 cents guess is that it has to do with the app being document based but... not really sure
Connect the menu items to the application scene's First Responder. When you connect to the application scene's First Responder, your view controller's IBActions should appear in the HUD's list of available actions instead of the action segues shown in your screenshot's HUD.
Why can't I connect my menu to my view controller IBAction?
Because your menu items and view controller are in different scenes in the storyboard. You can think of a scene as an independent graph of objects that are instantiated when the scene is loaded from the storyboard. Objects in different scenes can't be connected together in the storyboard because they're not loaded at the same time.
Just for fun, try creating an instance of your view controller in the Application Scene in your storyboard. To do that, you'll probably need to drag a plain old NSObject instance into the scene and then set its type. Once you do that, you'll find that you can drag a connection from a menu item to that view controller just as you'd expect, but you can't drag a connection to a different object of the very same type in a different scene.
Note: Once you've played around enough to convince yourself that it works, remember to delete the view controller that you added. A view controller without a view is like a duck without a quack, and a view controller and its view hierarchy should be in their own scene.
My 2 cents guess is that it has to do with the app being document based
No, it doesn't have anything to do with that. You'd have the same problem in an app that's not document-based. You'd also have the same problem if your app were .xib-based instead of using storyboards, since the controller you'd be trying to connect to would be in a completely different .xib file.
The easy solution, as Mark already described, is to use the responder chain. The First Responder proxy object is part of every scene, so you can always make connections to it. When you connect a menu item to First Responder its target will be nil, which tells NSMenu to walk the responder chain until it finds an object that responds to the menu item's action message. It then sends the message to that object.
If you are converting a project from objective C to Swift, do not make my mistake. When writing your IBAction write like this:
#IBAction func someAction(_ sender:AnyObject) {
// this will work
}
Do not omit the underscore before sender or the Interface Builder won't be able to connect to your action as in here:
#IBAction func someAction(sender:AnyObject) {
// this won't work and IB won't connect to this action
// because sender will be part of the symbol name
}

NSSplitViewController based application almost never launches with the correct size

I have this app that uses a NSSplitViewController as the root and has a NSTabViewController connected as its detailViewController.
This app is set to launch at 1024x768. The left pane should launch at 320x768 and the right pane (where the tabViewController is), should launch at 704x768.
From 10 times I run this app, 9 times it will launch with the incorrect size (about 500x500). Other strange thing is that this app should not be scalable, but if you hover the mouse near the window border you see cursor indication to scale.
I want this to launch at the correct size and have no scalable option.
Both of these settings are on interface builder but are being ignored.
You can download a sample project that demonstrates the problem, here. Stop and run the project several times to see the problem.
How do I solve this?
I couldn't say for sure what's causing the problem, but one way you may be able to solve it is to add some constraints. Interface Builder doesn't allow you to constrain the default NSView instances that it inserts into the left and right panels of the split view, so you'll need to add your own. The screen-shot below
is taken from your demo, but after I've done the following:
Added a subview to the left split (My Content View), and pinned it's edges to the edges of its superview (the view Xcode automatically adds to the splitview)
Added an explicit width constraint of 320 pixels to My Content View
When I load the app both splits are visible, the divider doesn't budge, and the window can't be resized.
Update - a better solution
Although constraints are one way to solve this problem, I think the root of the problem lies in a bit of unexpected behaviour in Interface Builder. When you drag an NSSplitViewController object onto the canvas, and make it the target of the window controller's content window relationship, the split-view controller's view outlet is not actually set. One consequence of this appears to be that, when you load the app, the divider will appear to be right over to one side. To resolve this, set the aforementioned view outlet to point at the split view:
I've created a demo project with a setup similar to that in the questioner's demo app.
For reference, the same problem occurs if the window content segue points to an NSTabViewController scene. New windows open with a size of 500x500.
I solved it by placing a plain view controller with a container view between my window and my main tab view controller. The window will then use the size of the container view as initial size.
Here is what I did in detail:
Added a new view controller scene to the storyboard
Made that view the size I want my window to use initially
Added a container view to the new view controller scene & added 4 constraints to have the container cover the view completely
Connected the window's content segue to the new view controller
Finally connect the container view to my actual tab view controller scene
Before:
[Window Controller Scene] → [Tab View Controller]
After:
[Window Controller Scene] → [View Controller Scene] → [Tab View Controller]
(with Container View)

Issues when reloading views in an NSBox

I have a problem when reloading views. OSX is not as simple as IOS.
In MainMenu.xib I have an NSBox. As per Hillegrath, several views are stored in an array and when a segmented button is pressed the views are exposed. This works properly. My frustration involves revisiting these views when changes have been made that should cause them to redisplay new values through [self someAction]. Actions in two of the views alter (global)values that should propagate changes in the other views. I am using a window controller w/xib (MainMenu) to hold the box which contains the views. I also title the views in code.
The global ivar values change properly when required and the log shows that. However dependent operations do not occur when the view are revisited, ie, update view specifics.
What appears to happen is that loadView is not called when the various views are displayed, ** **. awakeFromNib and loadView operate correctly when each view is first displayed but not ever on redisplays. This implies that that the view may be hidden but viewDidUnhide has no effect.
The view changing code (from an SO MVC answer and Hillegrath) is
NSViewController *activeVC =
(NSViewController *) self.viewControllers[index];
// [_box setContentView:nil];
[_box setContentView:activeVC.view];
[_box setNeedsDisplay:YES];
From the copied code it can be seen that I have also attempted setting the active content to nil before setting a new view but, to no avail.
Any ideas, recommendations, notifications fail to work either but may not set up correctly.
Thanks
It looks like you’re confusing view loading with display. -awakeFromNib is received by a nib file object only when the nib file is loaded. Similarly, -loadView is received by the view controller only when its corresponding nib file is loaded. Since you are keeping your view controllers in an array, those view controllers do not get deallocated, hence their corresponding nib files are never unloaded whilst the array is alive. This is reasonable behaviour, but you must bear in mind that -awakeFromNib and -loadView are only executed once in this case because nib loading is executed only once.
Since it seems that you are manually populating the view in -loadView, you’ll also have to do that whenever you set that view as the box’s content view (assuming you’re not always updating the view whenever the model changes). For instance, you could have a -reloadData method in your view controller and both -loadView and your box’s content view swapping method would use -reloadData.
Alternatively, you could set your model object as the represented object of your view controller and bind the controls in the corresponding view to properties of that represented object. NSViewController exposes a representedObject property that’s convenient for bindings.
For the record, -[NSBox setContentView:] marks the box for redisplay, so you don’t need to send it -setNeedsDisplay:YES.
(and make sure _box actually points to the NSBox instance)

NSTableView makeViewWithIdentifier across nibs

I have a similar question to Cocoa - View-Based NSTableView, using one cell in multiple tables, amplified by
Apple's own docs for makeViewWithIdentifier:owner:
"Typically identifier is associated with an external NIB in Interface Builder and the table view will automatically instantiate the NIB with the provided owner."
This seems to imply that you should be able to store the NSTableCellView in a separate nib from the nib containing the NSTableView. However, in my experimenting, I have only ever been able to obtain cells which are contained within the tableview I'm calling this on. I.e., if I cut and paste my cell into a new .xib file, the tableview can no longer find it. What am I doing wrong, or is this actually impossible and I am somehow misreading Apple's docs?
Use - (void)registerNib:(NSNib *)nib forIdentifier:(NSString *)identifier to register a nib to be used with a cell identifier.
If it doesn't work you're probably registering the nib after the tableView data has been loaded. Use [tableView reloadData] afterwords to be sure it's not a timing issue.
I just ran into this problem and I think you cannot use makeViewWithIdentifier:owner: when you're using a dedicated Nib to populate View-Based Tables.
The problem has to do with file owners (ie. view controllers). makeViewWithIdentifier:owner: seems intended to be used with "self" as the owner for simple custom views.
Generally if you have a separate nib for the custom view with outlets, you're going to want a separate view controller too. Otherwise, if your custom view has an outlet and the table displays many custom views, which outlet are you referring to from the "self" table view owner?
So in my test, I've got the AppDelegate as the delegate/datasource of the Table View. I have a CellView.xib, and CellViewController.h/.m with outlets to the interface. Then in my tableView:viewForTableColumn:row: delegate method I have this code:
SSCellViewController *vc = [[SSCellViewController alloc] initWithNibName:#"CellView" bundle:nil];
return vc.view;
What you lose is the cell re-use that happens automatically with makeViewWithIdentifier:owner:. To implement that yourself, you'll also likely have to deal with managing the many view controllers you've created.
I might still be missing something, as I'm coming to OS X development after years of only doing iOS work.

Resources