I want to make behavior like messaging app. I have been browsing Stack Overflow for solutions for this, and indeed there are plenty:
Leaving inputAccessoryView visible after keyboard is dismissed
This was the one that I found. But it seems things are a little different in iOS8. If I do the same thing in new iOS8 sdk, i get error:
'UIViewControllerHierarchyInconsistency', reason: 'child view controller:<UICompatibilityInputViewController: 0x7fdcb3441b10> should have parent view controller:<ViewController: 0x7fdcb3b1e9f0> but requested parent is:<UIInputWindowController: 0x7fdcb684c000>'
In order to test this more I made a sample project, just one controller with view on the bottom:
Outlet is connected to bottom view, that only has UITextField on it. Am I missing something and how do i get the desired behvior?
iOS8 has a retain cycle with the inputAccessoryView. Here's a good post that seems to have a good workaround:
http://derpturkey.com/uitextfield-docked-like-ios-messenger/
You are adding the someView to multiple superViews, which leads to inconsistent hierarchies (which it is telling you).
When the keyboard gets activated, it calls the inputAccessoryView() method to see if it needs to stick anything on top of the keyboard, and adds that to its own superView. But you already added it to the view through your storyboard.
Now there are 2 ways you can solve this:
Make a .xib with your view and return that one in your inputAccessoryView(), not adding it to any superview yourself (the keyboard will.
Or make it completely in code using NSLayoutConstraint.
You can add the following code to your ViewController which will persist the view even when the keyboard is hidden.
override func canBecomeFirstResponder() -> Bool {
return true
}
Look at this GitHub repo for an example.
Related
It's easy to enable the "inspector bar" for text views so that a bar appears at the top of the screen with various formatting buttons. (Although I had some confusion until I learned to make sure I was selecting the text view in a scroll view, and not the scroll view itself). I can either programmatically use [textView setUsesInspectorBar:YES] or go to the Attributes Inspector and check the "Inspector Bar" box in the "Uses" section.
My question is, how can I further control the inspector bar? I'm having trouble finding information on it in the XCode documentation or online. I'd like to be able to position it in a different place on the screen. Being able to pick and choose which specific controls are in the bar would be great too.
The answer is, you aren't meant to further control the inspector bar. There's nothing in the documentation because, well, there's nothing. Apple's saying, use it or don't use it.
However, if you dig into it a bit, you will find that the inspector bar is a very interesting control. It's not displayed as part of the text view, but rather (privately) embedded in the "window view" itself. When I say "window view," I mean the superview of the content view.
If you list the subviews of that "window view":
NSLog(#"%#", [self.testTextView.window.contentView superview].subviews);
You end up with:
2012-08-02 15:59:30.145 Example[16702:303] (
"<_NSThemeCloseWidget: 0x100523dc0>", // the close button
"<_NSThemeWidget: 0x100525ce0>", // the minimize button?
"<_NSThemeWidget: 0x100524e90>", // the maximize button?
"<NSView: 0x100512ad0>", // the content view
"<__NSInspectorBarView: 0x100529d50>", // the inspector view
"(<NSToolbarView: 0x10054e650>: FD2E0533-AB18-4E7E-905A-AC816CB80A26)" // the toolbar
)
As you can see, AppKit puts the inspector bar at the same level as other top level window controls. Now this is getting into the land of private APIs, but simply tinkering with the "window view" shouldn't get any apps rejected.
You can try to get a reference to the __NSInspectorBarView from here. It seems like it is always the subview right after the content view, so something like this may work:
NSArray *topLevelViews = [self.testTextView.window.contentView superview].subviews;
NSUInteger indexOfContentView = [topLevelViews indexOfObject:self.testTextView.window.contentView];
if (indexOfContentView + 1 < topLevelViews.count) {
NSView *inspectorBar = [topLevelViews objectAtIndex:indexOfContentView + 1];
NSLog(#"%#", inspectorBar);
}
NSLog(#"%#", topLevelViews);
Since this immediately breaks if Apple changes the ordering of the top level views, it may not be a good idea for an application for production. Another idea is:
NSView *inspectorBarView = nil;
for (NSView *topLevelView in topLevelViews) {
if ([topLevelView isKindOfClass:NSClassFromString(#"__NSInspectorBarView")]) {
inspectorBarView = topLevelView;
}
}
NSLog(#"%#", inspectorBarView);
I don't know if the use of NSClassFromString() will pass App Store review guidelines, however, since once again, it's dependent on private APIs.
That being said, once you get a reference to the inspector bar view, things still don't work too well. You can try repositioning it at the bottom:
if (inspectorBarView) {
NSRect newFrame = inspectorBarView.frame;
newFrame.origin = NSZeroPoint;
[inspectorBarView setAutoresizingMask:NSViewMaxYMargin | NSViewMaxXMargin];
[inspectorBarView setFrame:newFrame];
}
But you end up with a misdrawn toolbar, so more work would be necessary there:
My ideas would be to try to shift the content view's height up to cover up the gray left-over area (which would have to be done every time the window is resized, maybe tinkering with autoresizing masks may make it easier) and custom draw a background for the inspector bar at the bottom.
EDIT
Oh, and you should file a feature request for this too. bugreport.apple.com
This is four years late, but I feel like someone on the internet may benefit from this in the future. I spent way too long trying to figure this out.
The inspector bar class, as the others have pointed out, seems to be a private class (__NSInspectorBarView). Therefore, it's probably not recommended to modify.
Nevertheless! The curious have to know. The inspector bar is inserted, at the time of this post (April 2016) into the window's accessory bar. You can get a list of accessory views as of OS X 10.10 using the array property in NSWindow called titlebarAccessoryViewControllers[].
Here's some Swift 2.0 code to do just that, assuming you haven't inserted any other accessory views into the window beforehand.
if window.titlebarAccessoryViewControllers.count > 0 {
let textViewInspectorBar = self.titlebarAccessoryViewControllers[0].view
let inspectorBarHeight: CGFloat = textViewInspectorBar!.frame.height // 26.0 pt
}
It's worth noting that accessory views are handled differently in full screen mode apps: https://developer.apple.com/library/mac/documentation/General/Conceptual/MOSXAppProgrammingGuide/FullScreenApp/FullScreenApp.html
I personally would not attempt to move an accessory view, as they are special kinds of views designed to stay in the toolbar (if I fully understood what I have read).
NSTitlebarAccessoryViewController Reference:
https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSTitlebarAccessoryViewController_Class/
Another 3 years on, but I suspect some will find this useful. My specific problem was in having a window fully filled by a tabView - ideal for setting various kinds of user defaults. Only one of these tab pages had a couple of text views for which I wanted the inspector bar visible. Tabbing to that page made the inspector bar appear, and pushed the whole lot down, ruining my carefully planned layouts. Tabbing away from the page did not hide it again.
The obvious thing was to get the inspector bar to appear on the relevant tab page only. Having got hold of it ("on the shoulders of giants" - thanks to giant Vervious) it is relatively easy to reposition it in the view hierarchy. You are still left with the problem of space for an empty toolbar pushing the content down. The window's view hierarchy changes radically when the inspector bar first appears, and I gave up on trying to do anything with it.
My solution is to increase the content view's height. (Why height and not origin I can't say.)
func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
if let inspectorBar = window!.titlebarAccessoryViewControllers.first(where:
{$0.view.className == "__NSInspectorBarView"} )?.view {
// move content view back to where it should be
var sz = window!.contentView!.frame.size
sz.height = window!.frame.size.height - 21
window!.contentView?.setFrameSize(sz)
// put the inspector where we want it
inspectorBar.removeFromSuperview()
let y = textPage.frame.size.height - inspectorBar.frame.size.height - 10
inspectorBar.setFrameOrigin(NSPoint(x: 0, y: y))
textPage.subviews.insert(inspectorBar, at: 0)
}
}
The code belongs in a NSTabViewDelegate which I made my window controller conform to, remembering to set the tabView's delegate to File's Owner in the xib, and is called whenever a new tab is selected. textPage is the view inside the relevant tabViewItem.
There are some arbitrary constants found by trial and error. The function only need run once. Repeated calls are harmless, but you could put in a flag to make an early return from subsequent calls.
You cannot do anything to position this thing.
Clearly, the corruption noted by #Vervious is real, but only if you do not have an NSToolBar.
You see, this inspectorBar is sadly a mostly private and mostly (publicly) undocumented but awesome tool. And it is very much intended for use in a window that has an NSToolBar visible... go figure.
After you have a toolbar added to your view
Still with a toolbar but hidden, and inspector bar is cool
(as in via the view menu or the method it invokes, which is toggleToolBarShown: and is an NSResponder friendly message )
So it is obvious, no you cannot do much with this. It's design is poorly documented. It works as intended as a pseudo accessory view bar under the place an NSToolbar goes (which is also not adjustable)
The issue is as follows: I have an iOS 9 app (Swift) with a nav bar that houses a bunch of UIBarButtonItems with segues to several UINavigationControllers that finally display tables, views etc. as popovers. Of course tapping one of these buttons does not automatically disable the other UIBarButtonItems in the nav bar and the user is free to tap them. With a popover already displaying however we get the following warning as this is against the HIG and also grounds for rejection:
Attempt to present UINavigationControllerViewController which is already presenting (null)
To take care of the issue once a popover is displaying a) the remaining UIBarButtonItems need to be disabled and b) displaying additional popovers needs to be halted.
So what is the best cause of action here? The isBeingPresented flag of the popover's view controller is not available in the governing view controller because one can't create outlets of one view controller in another and the navigationController's viewControllers array doesn't include external navigation controllers or their descendants.
My lame workaround involves a global appIsPresentingPopover Bool which is set to true in viewDidLoad and to false in viewDidDisappear in the mentioned view controllers. That Bool is then tested in the governing view controller's prepareForSegue method to stop additional popovers from displaying. It is also kvo'ed to disable any enabled UIBarButtonItems which now all have an outlet. Ugly!
What is the standard way of handling this common scenario?
Surprisingly simple solution and much more elegant than my initial attempt. I added segue identifiers for all involved popups and a UIViewController class variable. In prepareForSegue I set the variable to the destination view controller. Then in shouldPerformSegueWithIdentifier I simply check whether that popover variable is nil. If it is not, I dismiss the UIViewController and set it to nil. Finally I also followed Beau Nouvelle's suggestion and kept those buttons lit so users can switch directly between popovers. Thanks again.
Xcode 7.0.1 (7A1001)
iOS 9.0
I made a custom #IBDesignable UIView, only implementing the drawRect function. Like so:
#IBDesignable
class CustomView: UIView {
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
// Lots more drawing code after this
// ...
}
}
While I was writing this drawRect, I had a storyboard with several different instances of this rendering on a view controller. When I made a change in drawRect and saved, these changes would be reflected in the storyboard in close to real time. There was even an IBInspectable String which allowed me to change an enum type and draw differently depending on that. The views could be resized in the storyboard, and the new width and height would be accounted for in my drawing code, and the drawing would be scaled and translated, exactly as I intended.
This was all working fine, and I had not made any changes to that UIView subclass, but I did delete my test views in the storyboard while building out newer views which would actually end up using this custom view.
After doing a lot of dancing around with constraints, I was ready to place my custom view where it belonged. But it does not work. While the identity inspector acknowledges my custom class as an IBDesignable, it hangs on "Updating" status.
To prove I wasn't going crazy, I made entirely new #IBDesignable UIView subclasses which implemented simple drawRects or even completely empty drawRects. These are now having the same issue and are not rendering on the storyboard.
Note that I can run this in the simulator and the drawing appears as I expect at runtime. So what happened to the storyboard's ability to give that nice feedback I expect from an IBDesignable?
Things I have tried:
Clean / clean build folder / clearing XCode Derived Data directory
Restarting XCode
Deleting / commenting out drawRect guts
Making the most simple #IBDesignables with HelloWorld-like tutorials
Using existing known working #IBDesignables
Creating fresh UIViewController in storyboard to place custom view
Toggled: Editor > Automatically Refresh Views
Editor > Refresh All Views
Editor > Debug Selected Views
All these attempts continue to show "Updating" in Custom Class section of the identity inspector.
Clearly something beyond my implementation is causing a problem here. Anybody else having this issue? Can anybody point me in the right direction? Not sure what else to try.
In the Identity inspector where it says "Module" add your AppName
I'm fairly new to Mac development and am slightly confused by the new "storyboard" feature in Xcode 6. What I'm trying to do is segue from one view controller to another in the same window. As of right now, all the different NSViewControllerSegues present the view controller in a new window, be it a modal or just another window. What I'd like to do is just segue within the same window, much in the same way one would on iOS (though an animated transition is not crucial). How would this be achieved?
If you provide a custom segue (subclass of NSStoryboardSegue) you can get the result you are after. There are a few gotchas with this approach though:
the custom segue will use presentViewController:animator so you will need to provide an animator object
because the presented view is not backed by a separate Window object, you may need to provide it with a custom NSView just to catch out mouse events that you don't want to propagate to the underlying NSViewController's view
there's also a Swift-only glitch regarding the custom segue's identifier property you need to watch out for.
As there doesn't seem to be much documentation about this I have made a small demo project with custom segue examples in Swift and Objective-C.
I also have provided some more detail in answer to this question.
(Reviving this as it comes up as first relevant result on Google and I had the same problem but decided against a custom segue)
While custom segues work (at least, the code given in foundry's answer worked under Swift 3; it needs updating for Swift 4), the sheer amount of work involved in writing a custom animator suggests to me that their main use case is custom animations.
The simple solution to changing the content of a window is to create an NSWindowController for your window, and to set its contentViewController to the desired viewController. This is particularly useful if you are following the typical pattern of storyboards and instantiate a new ViewController instance every time you switch.
However.
The NSStoryboard documentation says, quite clearly in macOS, containment (rather than transition) is the more common notion for storyboards which led me to look again at the available tools.
You could use a container view for this task, which adds a NWViewController layer instead of the NSWindowController outlined above. The solution I've gone with is to use an NSTabViewController. In the attributes inspector, set the style to 'unspecified', then select the TabView and set its style to 'tabless'.
To change tabs programatically, you set the selectedTabViewItemIndexof your TabViewController.
This solution reuses the same instance of the ViewControllers for the tab content, so that any data entered in text fields is preserved when the user switches to the other 'tab'.
Simple way with no segues involved to replace the current view controller in the same window:
if let myViewController = self.storyboard?.instantiateController(withIdentifier: "MyViewController") as? MyViewController {
self.view.window?.contentViewController = myViewController
}
I have a UIView filled with buttons that are all nicely hooked up to actions and outlets. However, in my infinite wisdom, I decided that I really would rather have the button behavior to be different and subclassed a UIControl.
My plan was to hop into Interface Builder, change the class of the buttons to my new UIControl subclass, and then be up and running. This would preserve all of outlet and action connections.
In IB (View Identity Inspector) when I type in my UIControl subclass into the class field, it reverts back to UIButton when I tab out. Any UIButton subclass works, but not a UIControl. I can go down the inheritance tree but not up....
The first plan was to go to XCode and change the superclass of my new control temporarily to UIButton, change the 'class' of my IB buttons, and then change the XCode code superclass back to UIControl. IB accepted and changed the class, but running the app gives me non-visible buttons. The IB Attributes inspector still shows it as a button.
Creating the control from scratch and rewiring works, but I was hoping to not rewire all the buttons if it could be avoided. (This is a change I was hoping to roll across multiple apps, so it is a bit more painful that it sounds)
Anyone know any way around this?
many thanks!
I'm a year late on this, but maybe it will help someone in the future. Perhaps you could open the .xib in a text editor and figure out what text you would have to change to get it to work (try it on a sample project first), then use find and replace to fix all of them at once.