Im trying to make so if the user clicks on the menu icon to show the popover that the popover closes if the user clicks anywhere but popover. I set the behavior to transient but thats not doing what I thought.
Now if the user clicks somewhere on the popover bringing focus to it, then the user can click somewhere else on the screen and the popover will close. If I could force a focus to the popover I think that would fix my problem as well. Unfortunately I dont know how to do that either.
class AppDelegate: NSObject, NSApplicationDelegate {
let view : NSView!
let statusItem: NSStatusItem
let popover: NSPopover
let button : NSButton!
override init() {
statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
if let statusButton = statusItem.button {
appStatusButton = statusButton
statusButton.image = NSImage(named: "icon128off")
statusButton.alternateImage = NSImage(named: "icon128")
statusButton.action = "onPress:"
}
popover = NSPopover()
popover.animates = false
popover.contentViewController = ViewController()
popover.behavior = .Transient
}
}
Here is the view controller
class ViewController: NSViewController, WKNavigationDelegate{
var webView : WKWebView!
override func loadView() {
view = NSView()
view.translatesAutoresizingMaskIntoConstraints = false
view.addConstraint(NSLayoutConstraint(
item: view, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 580))
view.addConstraint(NSLayoutConstraint(
item: view, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 425))
}
}
Swift 5.1
Problem is that the PopOver's Window is not becoming key, to fix this just force it to become key just after showing it.
For example supposing pop is a NSPopOver:
pop.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
pop.contentViewController?.view.window?.makeKey()
Swift 5:
NSApplication.shared.activate(ignoringOtherApps: true)
Add this right before you open the popup
NSApplication.sharedApplication().activateIgnoringOtherApps(true)
Thanks to this guy!
I have faced with the exact same issue, none of the answers works for me. But i tried a combination of showing below which works flawlessly
self.popover.behavior = NSPopover.Behavior.transient
NSApp.activate(ignoringOtherApps: true)
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
I do not recommend to use behaviour property of the popover. It is better to handle it in your application. see how it is done in this link I answered almost same problem.
Xcode Swift OS X popover behavior
With addition to John Pollard's answer
If you're using Swift 4+,
Add this code before showing the popup:
NSApplication.shared.activate(ignoringOtherApps: true)
Related
I want to make a scroll view programmatically in xcode and want to add anchor constraints using safe area layout guide Auto Layout. And want to add some text views button and map init but could not find any proper way to do this. I have tried many codes. What is the proper code for this?
Please try below code for programmatically create Scroll view and add UIView inside XCode
Swift 4.0
import UIKit
class ViewController: UIViewController {
let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let myView: UIView = {
let view = UIView()
view.backgroundColor = .yellow
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scroll view to self.view
self.view.addSubview(scrollView)
// constrain the scroll view to 8-pts on each side
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
//Frame for UIView here
myView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
scrollView.addSubview(myView)
}
}
I want to change the background and text of the button on click, I tried several SO solutions but they haven't worked, you can see what I tried in my project:
https://github.com/jzhang172/modalTest
I tried debugging it by putting a simple print statement and it looks like it doesn't ever go to it.
UIButton's have a method for setting the title color. So if you had a UIButton IBOutlet named myBtn:
myBtn.setTitleColor(UIColor.blackColor(), forState: .Highlighted)
and to change the text of the button on touch:
myBtn.setTitle("This button was touched", forState: .Highlighted)
As far as setting the background color, you could add an extension for your UIButton which allows you to do this:
extension UIButton {
private func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRectMake(0.0, 0.0, 1.0, 1.0)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func setBackgroundColor(color: UIColor, forUIControlState state: UIControlState) {
self.setBackgroundImage(imageWithColor(color), forState: state)
}
}
Then you could do:
myBtn.setBackgroundColor(UIColor.grayColor(), forUIControlState: .Highlighted)
Hope this helps!
SOLUTION:
1) Create an IBAction from your UIButton and also an IBOutlet called button.
EDIT: As per your request (How to trigger the even when the button is TOUCHED, not RELEASED?):
2) Do this:
#IBAction func changes (sender: UIButton) {
button.backgroundColor = UIColor.blueColor()
button.setTitle("Button Title", forState: UIControlState.Normal)
}
Your UIButton #IBOutlet closePop is hooked up to a single #IBAction of the exact same name (closePop()). That closePop() IBAction ONLY dismisses the helpViewController - it doesn't do anything about the text or button color.
Your other #IBAction function like, in which you try to set the color & print "Is this even working?", is not hooked up to the button, and is never called.
what is the best way to display a bunch of NSViewControllers horizontal side by side inside a horizontal scrollable view?
Each "row" must be resizable by its own like a split view.
I tests a few ideas with split view and scroll view but can't get a good starting point.
Thanks for a kick.
ps.
UPDATE
here is what i've got:
I add a scrollview to my main view (ColoumnMasterViewController) border to border. I add a second ViewController to the storyboard and named them "coloumn_view_controller"
In the ColoumnMasterViewController i add the coloumn_view_controller.view a couple of times:
class ColoumnMasterViewController: NSViewController {
#IBOutlet weak var scrollView: NSScrollView!
override func viewDidLoad() {
for i in 1...10 {
let vc: NSViewController = self.storyboard?.instantiateControllerWithIdentifier("coloumn_view_controller") as! NSViewController
vc.view.setFrameOrigin(NSPoint(x: (130 * i), y: (i * 10)))
println("set to \(vc.view.bounds)")
scrollView.addSubview(vc.view)
}
scrollView.needsLayout = true
}
}
But scrolling is not available.
So, how can i create a view inside a scrollview that is bigger than the current viewport? I suppose if i solve this, I'm able to fix my other splitview problems.
Thanks a lot!
UPDATE 2
Finally i get it so far:
#IBOutlet weak var scrollingView: NSScrollView!
override func viewDidLoad() {
var contentView = NSView(frame: NSRect(x: 0, y: 0, width: 3000, height: self.view.frame.height))
contentView.wantsLayer = true
contentView.layer?.backgroundColor = NSColor.redColor().CGColor
for i in 0...10 {
let vc: NSViewController = self.storyboard?.instantiateControllerWithIdentifier("coloumn_view_controller") as! NSViewController
vc.view.setFrameOrigin(NSPoint(x: (i * 130), y: 0))
vc.view.setFrameSize(NSSize(width: 130, height: self.view.frame.height))
contentView.addSubview(vc.view)
}
scrollingView.documentView = contentView
autoLayout(contentView)
}
but my autoLayout() didn't work so well. How would you implement autoLayout that the contentView will pin on top, bottom, trailing and leading of the superview?
You could use a split view controller. You can have as many dividers as you want.
In recent versions of OSX view controllers can host the content of other view controllers.
Make a "Super View Controller"
Make the "Super View Controller" display the view property of each sub UIViewController
The "Super View Controller" should probably be a UITableViewViewController instance to help you scrolling and resizing rows
Question is quite broad, so it's unclear how much guidance do you need, don't hesitate to comment to request clarifications.
Finally i found a solution and stick it together into a demo projekt: https://github.com/petershaw/DynamicScrollViewExample
The Trick is to add a NSView and set it into the documentView, than manage the documentView correctly with autolayout:
override func viewDidLoad() {
contentView = NSView(frame: NSRect(x: 0, y: 0, width: 3000, height: self.view.frame.height))
scrollingView.documentView = contentView
autoLayout(contentView!)
}
Autolayout:
func autoLayout(contentView: NSView){
let topPinContraints = NSLayoutConstraint(
item: contentView
, attribute: NSLayoutAttribute.Top
, relatedBy: .Equal
, toItem: contentView.superview
, attribute: NSLayoutAttribute.Top
, multiplier: 1.0
, constant: 0
)
let bottomPinContraints = NSLayoutConstraint(
item: contentView
, attribute: NSLayoutAttribute.Bottom
, relatedBy: .Equal
, toItem: contentView.superview
, attribute: NSLayoutAttribute.Bottom
, multiplier: 1.0
, constant: 0
)
println("heightContraints \(contentView.superview!.frame.height)")
let heightContraints = NSLayoutConstraint(
item: contentView
, attribute: NSLayoutAttribute.Height
, relatedBy: .Equal
, toItem: contentView.superview!
, attribute: NSLayoutAttribute.Height
, multiplier: 1.0
, constant: 0
)
println("widthtContraints \(contentView.superview!.frame.width)")
let calculatedWith: CGFloat = (CGFloat) (contentView.subviews.count * 130)
widthtContraints = NSLayoutConstraint(
item: contentView
, attribute: NSLayoutAttribute.Width
, relatedBy: .Equal
, toItem: nil
, attribute: NSLayoutAttribute.Width
, multiplier: 1.0
, constant: 0
)
widthtContraints!.constant = calculatedWith
NSLayoutConstraint.activateConstraints([
topPinContraints
, bottomPinContraints
, heightContraints
, widthtContraints!
])
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.needsLayout = true
}
and than do a layout update after resizing the content:
func updateWidth(){
let calculatedWith: CGFloat = (CGFloat) (contentView!.subviews.count * 130)
if (widthtContraints != nil) {
widthtContraints!.constant = calculatedWith
}
}
But I don't know if this is a good solution? Comments are welcome. Now, I can replace the view with an NSSplitView subclass.
I have a toolbar button
#IBOutlet weak var testButton: NSToolbarItem!
The button call a popover and works fine.
But if i try and call the popover from a top menu item i get a crash.
I have amended the location of the popover to appear below the testButton just as it normally would. (commented below)
#IBAction func menuPreviewAndTestAction(sender: AnyObject) {
var returnedHtmlString = checkEverythingAndCreateTheEncodedHtml(testButton)
setEncodedHtmlToPreview(returnedHtmlString)
var thebounds = self.testButton.view?.bounds // so i am givving bounds of button that narmally calls poover
testingPopover.showRelativeToRect(thebounds!, ofView: sender as NSView, preferredEdge: NSMaxYEdge) // crashes
}
Oh dear... I missed changing the ofView, all sorted with :
var thebounds = self.testButton.view?.bounds
var theview = self.testButton.view
testingPopover.showRelativeToRect(thebounds!, ofView: theview! as NSView, preferredEdge: NSMaxYEdge)
(Xcode6, iOS8, Swift, iPad)
I am trying to create a classic Web-like modal view, where the outside of the dialog box is "grayed-out." To accomplish this, I've set the alpha value of the backgroundColor of the view for the modal to 0.5, like so:
self.view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
The only problem is that when the modal becomes full-screen, the presenting view is removed. (Ref Transparent Modal View on Navigation Controller).
(A bit irritated at the concept here. Why remove the underlying view? A modal is, by definition, to appear atop other content. Once the underlying view is removed, it's not really a modal anymore. it's somewhere between a modal and a push transition. Wa wa wa... Anyway..)
To prevent this from happening, I've set the modalPresentationStyle to CurrentContext in the viewDidLoad method of the parent controller, and in Storyboard... but no luck.
self.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
How do I prevent the presenting view from being removed when the modal becomes full screen?
tyvm.. more info below.
Also in Storyboard, like so (Presentation: Current Context)
Thx for your help... documentation below:
First, remove all explicit setting of modal presentation style in code and do the following:
In the storyboard set the ModalViewController's modalPresentation style to Over Current context
Check the checkboxes in the Root/Presenting ViewController - Provide Context and Define Context.
They seem to be working even unchecked.
You can try this code for Swift:
let popup : PopupVC = self.storyboard?.instantiateViewControllerWithIdentifier("PopupVC") as! PopupVC
let navigationController = UINavigationController(rootViewController: popup)
navigationController.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
self.presentViewController(navigationController, animated: true, completion: nil)
For swift 4 latest syntax using extension:
extension UIViewController {
func presentOnRoot(`with` viewController : UIViewController){
let navigationController = UINavigationController(rootViewController: viewController)
navigationController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.present(navigationController, animated: false, completion: nil)
}
}
How to use:
let popup : PopupVC = self.storyboard?.instantiateViewControllerWithIdentifier("PopupVC") as! PopupVC
self.presentOnRoot(with: popup)
The only problem I can see in your code is that you are using CurrentContext instead of OverCurrentContext.
So, replace this:
self.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.navigationController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
for this:
self.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
self.navigationController.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
This worked for me in Swift 5.0. Set the Storyboard Id in the identity inspector as "destinationVC".
#IBAction func buttonTapped(_ sender: Any) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let destVC = storyboard.instantiateViewController(withIdentifier: "destinationVC") as! MyViewController
destVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
destVC.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(destVC, animated: true, completion: nil)
}
The problem with setting the modalPresentationStyle from code was that you should have set it in the init() method of the presented view controller, not the parent view controller.
From UIKit docs: "Defines the transition style that will be used for this view controller when it is presented modally. Set
this property on the view controller to be presented, not the presenter. Defaults to
UIModalTransitionStyleCoverVertical."
The viewDidLoad method will only be called after you already presented the view controller.
The second problem was that you should use UIModalPresentationStyle.overCurrentContext.
The only way I able to get this to work was by doing this on the presenting view controller:
func didTapButton() {
self.definesPresentationContext = true
self.modalTransitionStyle = .crossDissolve
let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
let navController = UINavigationController(rootViewController: yourVC)
navController.modalPresentationStyle = .overCurrentContext
navController.modalTransitionStyle = .crossDissolve
self.present(navController, animated: true, completion: nil)
}
I am updating a simple solution. First add an id to your segue which presents modal. Than in properties change it's presentation style to "Over Current Context". Than add this code in presenting view controller (The controller which is presenting modal).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let Device = UIDevice.currentDevice()
let iosVersion = NSString(string: Device.systemVersion).doubleValue
let iOS8 = iosVersion >= 8
let iOS7 = iosVersion >= 7 && iosVersion < 8
if((segue.identifier == "chatTable")){
if (iOS8){
}
else {
self.navigationController?.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
}
}
}
Make sure you change segue.identifier to your own id ;)