Why Is My Set Up Cutting Off My Constraints To Margins? - xcode

I'm working from a storyboard connected to a UIViewController as such:
UPDATE:
The Problem Seems to be that my constraints do not pin to margins. Here are my constraints pinning to the bottom.
Here is how that translates to different devices.
I have messed with the various size settings shown in the second to last picture. I will try a more systematic approach to changing these if someone isn’t able to very easily detect why this is occurring. It seems unrelated to the Page Control and more related to page sizing
Project Github HERE
Note again, this is the exact same problem I get even when creating the view controller in the method used at:
HERE
I chose the code used as opposed to the code at the link only because I had already coded this and they were identical in issue, I have become aware sense then that this is a more complicated way to accomplish this though.
The issue is recreated perfectly with a simple constraint to the bottom.

There are many issues with your code / storyboard, but I think the problem you're complaining about is here:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
if view is UIScrollView {
view.frame = UIScreen.main.bounds // *
} else if view is UIPageControl {
view.backgroundColor = UIColor.clear
}
}
}
Remove the line that I've commented.
(If you also change the color in the last line to .red, just as an experiment, you will be able to see the page control, and you'll see that the text view is behaving consistently on different sizes of device.)

Related

Toolbar of NSScrollView inside SplitView [duplicate]

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)

NSWindow launching with wrong size after setting contentViewController to NSTabViewController

I have an Xcode project with an NSWindowController whose contentViewController was set to a subclass of NSViewController. I recently removed the NSViewController subclass from the storyboard and replaced the contentViewController with an NSTabViewController subclass.
Now, when I run the application, the NSWindow opens with a size of 500x500 instead of the size of the first tab. What's more, there is no view I can see in the storyboard that has a size of 500x500, and that size isn't being programmatically, either. The window itself is set to a different size, as is the view in the NSTabViewController's first NSViewController.
I'm assuming that there is some sort of constraint I have to set somewhere, but if there is, I don't know where/how to find it. Using Xcode 9.2 and High Sierra.
Programmatically setting the window's size to the correct size in windowDidLoad() works, but if I ever change the size of the view, I'll have to change that, as well, which will get old, quick.
Sorry if this is vague; I genuinely have no clue what kind of screenshot or code snippet would be helpful.
I recently ran into this frustrating problem as well.
There are a couple options to workaround this problem:
As you mentioned, set preferredContentSize in each of your custom view controllers that hold the tab's content to your desired size. This is inflexible but it does work.
// Swift
class FooViewController: ViewController {
override func viewWillAppear() {
super.viewWillAppear()
preferredContentSize = NSSize(width: 400, height: 280)
}
}
I found a hint to a better solution in this SO answer. You can add a subview (stackview, nsview, etc...) to the main view of the view controller that handles the tab's content (phew!) and then add constraints that pin it to each edge and add constraints that set the size.
Here's a screenshot of what it looks like in Interface Builder. I added a Stack View and then added 6 constraints.
Hope this helps.
Joshua's answer with setting the preferredContentSize did the trick, all kudos to him! One remark worth making is that since this is done exclusively for the parent tab view controller it's a good idea to subclass it and move this handling into tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) delegate method, which gets invoked when the tab is selected:
override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
tabViewItem?.viewController?.preferredContentSize = tabViewItem?.view?.frame.size
// Alternatively: tabViewItem?.viewController?.preferredContentSize = tabViewItem?.view?.fittingSize
super.tabView(tabView, didSelect: tabViewItem)
}
This way the preferred content size is always up to date and you can worry not about manually refreshing it, assuming the view provides the correct frame size or fitting size, which is easily achieved with constraints.
This method also get's invoked after the window controller finishes loading and where the 500×500 gets initially set.
Setting the preferred content size in every tabbed view controller itself is not ideal: the same code is duplicated across multiple controllers and adds unnecessary noise if these controllers are reused else where.
I had a similar issue. I added a view controller with a container view as the window content and pointed the container view content to the tab view controller.

IBOutletCollections alternatives in OSX (2016 Redux) for custom radio buttons

I'm trying to implement custom buttons as a radio button group. As I've found no way to customize radio button appearance (on/off/hover for each state), I've implemented them as custom image buttons (NSButton subclass). That part works fine. The issue is handling the states of each group of buttons and the corresponding variables determined by the groups (1 per group). IBCollections looks like a great fit for this problem. But I'm writing a Mac app.
The few questions I can find on here on IBOutletCollections in OS X* are a couple of years old. I can't even comment on them to ask since I lack the reputation points, thus the new question.
I did upvote the answers as they helped immensely as the blog posts on IBCollections I read didn't bother to specify them as iOS/UIKit only nor did XCode display any errors (SO HELPFUL! /sarcasm).
Has anyway come up with an alternative way to accomplish this? Separate outlets for each is madness for a group of buttons like these (or worse multiple groups). Can I implement them as an array for IB defined buttons with IBOutlets and IBActions?
*soon to revert to Mac OS (though it will capitalization challenged :)
I won't claim it's an elegant solution, but I managed to solve my problem by grouping the custom buttons with NSArray arrayWithObjects: for each button group.
I added two methods to emulate radio button behavior (in this case for custom color buttons):
-(void)setButtonSelected:(NSArray*) theButtons toColor:(NSString*)color {
[self clearAllButtons:theButtons];
for (ColorButton *button in theButtons) {
if ([button.color isEqualToString:color]) {
button.state = NSOnState;
}
}
}
-(void)clearAllButtons:(NSArray*) theButtons {
for (ColorButton *button in theButtons) {
button.state = NSOffState;
}
}
As my specific problem involved more than one group of buttons that required unique selections, I then added arrays for the each of the aligned options in the groups (columns basically).
- (void)clearColorConflicts:(NSArray*)theButtons forRow:(NSString*)row {
for (ColorButton *button in theButtons) {
if (![button.row isEqualToString:row]) {
if (button.state == NSOnState) {
button.state = NSOffState;
}
}
}
}
Again, it may not be an elegant solution but it solved my problem without IBOutletCollection.
I did look into NSMatrix but I wasn't able to use custom image button behavior (on, off, hover_on, hover_off) when I grouped the custom buttons as a matrix.
The column groups could be handled by the two original methods but I have additional steps for variable/defaults updating when rows/category selections are cleared by selection in another row. In hindsight I could have checked the state of each group after selection instead.
My problem may have been unique, but I'm posting this in case it helps someone else.
I'm not marking as solved as I hope someone has a better solution than mine.

Previously rendered IBDesignable hangs on "Updating" in storyboard

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

Leaving inputAccessoryView visible after keyboard is dismissed iOS8?

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.

Resources