Weird rectangle (NSBannerView) in left top of NSTableView - macos

I'm working on an macOS app and I'm experiencing a weird issue. In the left top corner of my NSTableView (above the header), a grey rectangle is shown:
(I've added an NSBox behind the NSTableView to make it more clear in the screenshot)
With the Debug View Hierarchy, I've seen it's an NSBannerView which is added to the Scroll View wrapping the Table View.
My UI is built with Interface Builder (a storyboard). I've checked and unchecked lots of checkboxes in the Interface Builder but can't find what it is. Google and Stack Overflow also don't give any clues. Googling for "NSBannerView" even only yields some macOS header files.
How to get rid of the rectangle?
Environment details
macOS Mojave 10.14 Beta (18A365a)
Xcode 9.4.1 (9F2000)

Subclass NSTableRowView and overwrite layout, where you hide the view
- (void)layout {
[super layout];
for (NSView * v in self.subviews) {
if ([v.className isEqual:#"NSBannerView"]) {
v.hidden = YES;
}
}
}

Making this hiding thing into didAddSubview might be a better solution like the example below. Because layout is called each time you select the row.
override func didAddSubview(_ subview: NSView) {
super.didAddSubview(subview)
if subview.className == "NSBannerView" {
subview.isHidden = true
}
}

I use currently use this to correct a ugly looking NSBannerView:
func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int) {
if rowView.isGroupRowStyle {
for view in rowView.subviews {
if let effect = view.subviews.first as? NSVisualEffectView {
effect.material = .contentBackground // Or something else, the default material for group headers is .headerView
}
}
}
}

Related

status bar disappeared after full screen video in WKWebView only in iOS 12

As you can see this only happened in iOS 12.
iOS 12              iOS 11
 
here is my code:
import UIKit
import WebKit
class ViewController: UIViewController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override var prefersStatusBarHidden: Bool {
return false
}
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: UIScreen.main.bounds)
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
webView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
webView.loadHTMLString("<p><iframe src=\"https://www.youtube.com/embed/HCjNJDNzw8Y\" width=\"560\" height=\"315\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", baseURL: URL(string: "https://www.youtube.com/"))
setNeedsStatusBarAppearanceUpdate()
}
}
my info.plist is right below:
Does anyone know how to solve it?
I know that if I set the key View controller-based status bar appearance to YES will help, but In that case it will look like this:
There are unknown reason for changing status bar from white and black, and as for my real project is in a large scale, so it will be nice to solve in the original setting, rather than make every ViewController inherit from one class which is subclass from UIViewController or add dynamic for overriding prefersStatusBarHidden and preferredStatusBarStyle in extension (here just try to force it show update status bar when View controller-based status bar appearance set to YES)
Hope there is a solution for View controller-based status bar appearance set to NO, that will be very helpful thx.
here is the demo project, it was created by Xcode9.4, feel free to try it with.
Remove Following property from info.plist file. and Give it programmatically only.
Status bar is initially hidden : NO
View controller-based status bar appearance : NO
Status bar style : UIStatusBarStyleLightContent
It may be work for you.
Use this solution:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(videoExitFullScreen:) name:#"UIWindowDidBecomeHiddenNotification" object:nil];
...
}
- (void)videoExitFullScreen:(id)sender
{
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:YES];
}
Reference more here:
https://github.com/react-native-community/react-native-webview/issues/62
Create one extension of AVPlayerViewController like below this slo
extension AVPlayerViewController {
open override var prefersStatusBarHidden: Bool {
return false
}
}
In the end I used category in objective-c to deal with this problem.
I set View controller-based status bar appearance to YES and add code below
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
- (BOOL)prefersStatusBarHidden
{
return NO;
}
As for my experience, you have to also implement code above in UINavigationViewController.
It seems that it has its own implementation for that.
And also every window to see if there is a rootViewController exist, if there isn't, then add one for it.
Although this solution is a bit Cumbersome, but that is the one I solved my issue perfectly so far.
If you want the status bar to be white, technically to be .lightContent, follow the steps below:
In Info.plist, set View controller-based status bar appearance = YES
Create an extenstion to either UIViewController or UINavigationController, and write the following codes:
open override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
open override var prefersStatusBarHidden: Bool {
return false
}
Note: In case the status bar turns back to dark, you may need to trigger a call of self.setNeedsStatusBarAppearanceUpdate() inside viewWillAppear() to inform the status bar to refresh itself.
Hope that helps !!! Have a nice one!
Subscribe for
UIWindowDidBecomeHiddenNotification
event
NSNotificationCenter.DefaultCenter.AddObserver(new NSString("UIWindowDidBecomeHiddenNotification"), HandleAction);
and set the status bar state in handler:
void HandleAction(NSNotification obj)
{
UIApplication.SharedApplication.StatusBarHidden = false;
}

How to prototype NSTableRowView in Interface Builder

I have a view-based NSTableView and I'm trying to customize the appearance of certain rows.
I understand I need to implement the delegate method mentioned in the title; However I'm not sure about how to do it.
The documentation says:
You can use the delegate method tableView:rowViewForRow: to customize
row views. You typically use Interface Builder to design and lay out
NSTableRowView prototype rows within the table. As with prototype
cells, prototype rows are retrieved programmatically at runtime.
Implementation of NSTableRowView subclasses is entirely optional.
However, unlike cells, there is no NSTableRowView class in interface builder, nor is it clear how to setup a "prototype" row view.
I am trying something like this (Swift 3):
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView?
{
if (row % 4) == 0 {
// ..................................................
// [ A ] SPECIAL ROW:
if let rowView = tableView.make(withIdentifier: "SpecialRow", owner: self) as? NSTableRowView {
rowView.backgroundColor = NSColor.gray()
return rowView
}
else {
return nil
}
// ^ Always returns nil (Because I don't know how
// to setup the prototype in Interface Builder)
}
else{
// ..................................................
// [ B ] NORMAL ROW (No customization needed)
return nil
}
}
I have similar code working for cells -i.e., -tableView:viewForTableColumn:row:.
OK, so the obvious (?) solution worked:
On Interface Builder, drag and drop a plain-vanilla NSView into the table (it will only accept the drop in a specific column, not as a direct child of the table view).
Go to the Identity Inspector for the just dropped view, and change its Class to "NSTableRowView".
Because just setting the .backgroundColor property as in my code does not work, I instead used this solution and added a box view as a child, and configured that in Interface Builder. I had to setup autolayout constraints between the box and the row view, so that it would stretch to the row view's actual size at runtime.
(Alternatively, I could have played with the wantsLayer property of the row view... )
UPDATE: It turns out the backgroundColor property I was using in my code is defined in NSTableRowView (NSView does not have such property, unlike UIView).
But it also gets overridden by the table view's setting (i.e., alternating rows or not), so instead I should customize it in this method:
func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int)
{
if (row % 4) == 0 {
rowView.backgroundColor = NSColor.controlAlternatingRowBackgroundColors()[1]
}
else{
rowView.backgroundColor = NSColor.clear()
}
}
...after it was added (and its background color configured by the table view).
(Incidentally, it turns out I do not need a custom row view after all. At least not to customize the background color)

Prevent NSTableRowView from changing highlight when not in focus?

I've got an NSSplitView with a table view in the left pane. I've noticed that when I have an item selected in the left pane and I change focus to the right pane, the left pane loses focus and the highlighted row's highlight color turns to gray and the text turns black.
I have overridden the highlight color by overriding drawSelectionInRect in NSTableRowView. By doing this, the highlight color remains the same custom color, but the text turns dark which looks wrong.
Can I either let the highlight color change when the table view is out of focus, or prevent the text from turning dark when it's out of focus?
It appears that OS X calls setBackgroundStyle on the row view and its subviews when you click on the other view in the split view. Interestingly it seems to only happen to NSOutlineView.
Since I have code in place already to handle changing my images to different images when rows are selected (for improved contrast), and I am explicitly setting the background style when I need to using outlineViewSelectionDidChange and outlineViewSelectionIsChanging I overrode setBackgroundStyle to be a no-op and I made my own custom setter which changed the _backgroundStyle ivar.
It looks like this:
#implementation TextFieldWithHighlighting
#synthesize backgroundStyle = _backgroundStyle;
- (NSBackgroundStyle) backgroundStyle {
return _backgroundStyle;
}
#synthesize secretBackgroundStyle = _secretBackgroundStyle;
- (NSBackgroundStyle) secretBackgroundStyle {
return _backgroundStyle;
}
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
}
- (void) setSecretBackgroundStyle:(NSBackgroundStyle)secretBackgroundStyle {
_backgroundStyle = secretBackgroundStyle;
self.needsDisplay = YES;
}
- (void)drawRect:(NSRect)dirtyRect {
if(_backgroundStyle == NSBackgroundStyleDark) {
if(self.originalTextColor == nil) {
self.originalTextColor = self.textColor;
}
self.textColor = [NSColor whiteColor];
} else {
if(self.originalTextColor) {
self.textColor = self.originalTextColor;
} else {
self.textColor = [NSColor colorWithCalibratedRed:0x40/255.0 green:0x40/255.0 blue:0x41/255.0 alpha:0xFF/255.0];
}
}
[super drawRect:dirtyRect];
}
#end
It only works for me because I am explicitly handling rows changing their selection and AFAIK I am not relying anywhere on the OS changing it for me. If I could do it again I'd just push for using the system highlight color in which case I get this functionality for free.
Just as a small contribution to your answer. This works as well with the weird contrast generated with the drawSelectionInRect implementation. Here's a Swift solution that also works implemented in an NSTableCellView:
override var backgroundStyle: NSBackgroundStyle {
get {
return self.backgroundStyle
}
set {
}
}

Why my UITextView text is not show from starting and hide under Navigation Bar

I wonder why my textview text didn't show up from starting text.
This is the image from my Xcode IB
When I run it on my iPhone 6 Simulator and others
Actually it should start from "Example Notification...."
Please help me out.
Those who didn't start UITextView from start,here is the answer.I am sure it worked 100% on iOS 8 and higher on any devices.
1.You need to deselect these at your view controller where textview is applied
Go to ViewController > Attribute Inspector > Search Extend Edges > Under Top Bars & Under Button Bars
2.Add the following code at your view controller
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.yourTextView.setContentOffset(CGPoint.zero, animated: false)
}
Special Thanks to #El Captain
To maintain UINavigationBar translucent and allow user to rotate the device while reading UITextView I used something like this:
private var firstLayout = true
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if firstLayout {
firstLayout = false
textView.setContentOffset(
CGPoint(x: 0, y: -topLayoutGuide.length), animated: false)
}
}
Tested on iOS 9 and iOS 12 Beta.
In my case it was iOS 13. UITextView was constrained to edges. It's content was set on did load, after that text view automatically scrolled to the bottom causing large title to shrink.
What helped me is to wrap setText in to async:
DispatchQueue.main.async {
textView.text = logs
}
You can also keep UINavigationBar isTranslucent and UIScrollViewContentInsetAdjustmentAutomatic,
Just to fix the textView is hidden by navigationBar, then add the below code:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[textView setContentOffset:CGPointMake(0, -self.view.layoutMargins.top) animated:NO];
}

How to use dismiss an iPhone popover in an Adaptive Storyboard

I am new to iOS development, and am trying to learn storyboarding, Swift, and the new features of iOS 8 at the same time.
I have created a very simple storyboard that uses a Popover presentation segue to display another view. On the simulator, if I run this for an iPad, it works as expected. However, if I run it for an iPhone, instead of a popover, it displays a full-screen view, on top of the original view. This is fine; however, there is no way to dismiss it and go back to the original screen.
I have watched the WWDC 2014 video "228 A Look inside presentation controllers" and they can show a dismiss button if they build the user interface entirely with code.
I have also watched the "411 What's new in interface builder" session, where they say that this can be done in Interface Builder, but they do not show it, promising to show how to do it in the lab, if anyone is interested. Unfortunately, I did not attend WWDC 2014, or know anyone who has. My Google searches have not returned anything helpful either.
You could add the navigation controller like this-
Set your popover view controller as the root view controller to a navigation controller.
Delete the popover segue that you are currently using
Reconnect the segue from the button you are displaying the popover from to the navigation controller.
On iPad you will get a popover and on iPhone you will get a modal presentation. Both the iPad and iPhone will show the navigation controller. Depending on your use case this may or may not be something you want. Here's a screen show of what the storyboard should look like.
If you really do want your view controller to always be a popover leave your storyboard the way it is and add something like this to your view controller that presents the popover-
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Your segue name"]) {
UIViewController *yourViewController = segue.destinationViewController;
yourViewController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popoverPresentationController = yourViewController.popoverPresentationController;
popoverPresentationController.delegate = self;
}
}
The view controller presenting the popover will also need to respond to this UIPopoverPresentationDelegate method
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller
{
return UIModalPresentationNone;//always popover.
}
Lastly, you could do the following to only add the navigation controller to the modal presentation of your view controller on the iPhone and leave the popover on iPad without a navigation controller.
Leave your storyboard as is.
The proper place to inject the navigation controller is in - (UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style. In order for this to be called we must set ourselves as the delegate of the UIPopoverPresentationController.
Once again we will do this in prepareForSegue:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Your segue name"]) {
UIViewController *yourViewController = segue.destinationViewController;
yourViewController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popoverPresentationController = yourViewController.popoverPresentationController;
popoverPresentationController.delegate = self;
}
}
Then we will do this in the delegate method that I mentioned above
-(UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style
{
UIViewController *presentedViewController = controller.presentedViewController;
UINavigationController *navigationController = [[UINavigationController alloc]
initWithRootViewController:presentedViewController];
UIBarButtonItem *dismissButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonItemStyleDone target:self action:#selector(done:)];
presentedViewController.navigationItem.rightBarButtonItem = dismissButton;
return navigationController;
}
Good Luck!
If what you want is a popover on your iPad but a modal sheet with a close button on your iPhone then you can do it without creating an extra navigation controller in storyboard for the popover.
In Xcode 6.3 storyboard, you simply hook up a view controller and designate the segue as a "Present as Popover"
The code below should go in the view controller that segues to the popover, not in the popover itself:
First you set up the popover delegate:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "myPopoverSegueName") {
let vc = segue.destinationViewController
vc.popoverPresentationController?.delegate = self
return
}
}
Then you add the delegate extension (below your view controller's code) and create the navigation controller / close button on the fly:
extension myViewController: UIPopoverPresentationControllerDelegate {
func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
let nav = UINavigationController(rootViewController: controller.presentedViewController)
nav.topViewController.navigationItem.leftBarButtonItem = btnDone
return nav
}
}
Then you add your dismiss function and you should be good to go:
func dismiss() {
self.dismissViewControllerAnimated(true, completion: nil)
}
I am not sure why you need to do storyboard setup for the Done button, all the work can be done programmatically with few lines of code. The important part is to implement some UIAdaptivePresentationControllerDelegate protocol methods exactly like below:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle
{
return .FullScreen
}
func presentationController(controller: UIPresentationController,
viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController?{
var navController:UINavigationController = UINavigationController(rootViewController: controller.presentedViewController)
controller.presentedViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action:"done")
return navController
}
Then, a simple method to implement the dismissing behavior for the popover in case it was presented in full screen:
func done (){
presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
and you done!
In my case, I had a small popup that I wanted to be a popup on both an iPhone and iPad - and wanted to avoid using a navigation bar with a Dismiss. Discovered that one needed to implement two delegate calls (Swift 3.0):
extension MyViewController : UIPopoverPresentationControllerDelegate {
// Needed for iPhone popup
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
// Needed for iPhone in landscape
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}
Its possible to do it with mimimal code whilst putting the logic into the storyboard instead. In the view controller that presents the popover, just put in the marker method
#IBAction func unwindToContainerVC(segue: UIStoryboardSegue) {
}
It does not need any code but needs to be present so you can control drag to the Exit icon later on when you use interface builder.
I have my popover content not take up the entire background view but have a small margin around it. This means you can use interface builder to create a tap gesture recogniser for this view. Control drag the gesture recogniser to the Exit icon which then pops up some Exit choices, one of which is the unwindToContainerVC method as seen above.
Now any tap around the edge (such as in an iPhone 4S scenario) takes you back to the presenting view controller.
Here is the connections inspector for the gesture recogniser:

Resources