I am having a weird issue with a button. So I have a NSViewController with many subviews in it. When I click a button, a new NSView with click gestures and buttons is added on top. But I can't press any of them, they don't respond unless a click for 2 seconds and then release. I've tried disabling the gestures of the holder but it didn't work. Any suggestions?
Well, some of the rest of us do. In my case, it's for buttons on a view in a sheet, so "many subviews" isn't likely it. My view controller for the sheet is about 100 lines. Still debugging...
At present the VC is as follows. The snp.makeConstraints calls are for SnapKit (from GitHub)
#objc
class ThreadEditSheetViewController: NSViewController {
/// The container for the graphics view
#IBOutlet var sheetView: NSView!
/// The information packet initialized by the invoking view controller
var info: ThreadEditInfo!
/// API
override func viewDidLoad() {
super.viewDidLoad()
}
/// API
override func viewWillAppear() {
guard let gvc = (try? self.bundleLoader(id: "GraphicsViewController")) as? GraphicsViewController else {
fatalUserAlert(error: AppError.UIConstructionFailure, message: "Can't find GraphicsViewController for ThreadEditSheetViewController")}
let gv = gvc.view
self.view.addSubview(gv)
// Spaces in title text move it left to avoid visual overlap with scroll bar. Don't know how to do it with
// constraints given the scrolling view
let done = makeButton(gvc: gvc, title: "done ", action: #selector(doneEditing(_:)))
done.snp.makeConstraints{ (make) in
make.top.equalTo(gv).offset(-5)
make.right.equalTo(gv).offset(-5)
}
let cancel = makeButton(gvc: gvc, title: "cancel", action: #selector(cancelEditing(_:)))
cancel.snp.makeConstraints{ (make) in
make.top.equalTo(gv).offset(-5)
make.left.equalTo(gv).offset(5)
}
self.view.becomeFirstResponder()
super.viewWillAppear()
return
}
func makeButton(gvc: NSViewController, title: String, action: Selector) -> NSButton {
let button = NSButton(title: title, target: self, action: action)
let gv = gvc.view
gv.addSubview(button)
button.backgroundColor = .clear
button.setButtonType(.momentaryChange)
button.isTransparent = true
return button
}
#objc
func doneEditing(_ sender: Any) {
self.dismissViewController(self)
}
#objc
func cancelEditing(_ sender: Any) {
self.dismissViewController(self)
}
}
Related
I created an app with a menubar button using swift and storyboard, when I click the close button the window closed and the menubar button still sits on the menubar, that's good. What I want to do next is reopen the window by clicking the menubar button. After searching I figured out I can use this code to bring it to the front, but it only works when the window is still open. How can I bring it back when it is closed or miniatured?
NSApplication.shared.activate(ignoringOtherApps: true)
This the appDelegate
class AppDelegate: NSObject, NSApplicationDelegate {
private var statusItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
statusItem = NSStatusBar.system.statusItem(withLength: 16.0)
if let button = statusItem.button {
button.image = NSImage(named: "remote-control")
button.image?.size = NSSize(width: 16.0, height: 16.0)
button.image?.isTemplate = true
button.action = #selector(bringToFront(sender:))
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
print("terminate")
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
#objc func bringToFront(sender: AnyObject?) {
NSApplication.shared.activate(ignoringOtherApps: true)
NSApp.windows.last?.makeKeyAndOrderFront(nil)
}}
This is the windowcontroller
class MainWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
window?.title = ""
let styleMask: NSWindow.StyleMask = [.closable, .titled, .miniaturizable]
window?.styleMask = styleMask
}}
Thanks
I got it.
In appdelegate use deminiaturize function does exactly what I want.
for window in NSApp.windows {
window.deminiaturize(nil)
}
Since I only have one window but NSApp.windows has two members, I think I can deminiatureize all the windows.
I'd like to print a message when an user taps twice on the remote of the Apple TV. I got this to work inside a single UIViewController, but I would like to reuse my code so that this can work in multiple views.
The code 'works' because the app runs without any problems. But the message is never displayed in the console. I'm using Swift 3 with the latest Xcode 8.3.3. What could be the problem?
The code of a UIViewController:
override func viewDidLoad() {
super.viewDidLoad()
_ = TapHandler(controller: self)
}
The code of the TapHandler class
class TapHandler {
private var view : UIView?
required init(controller : UIViewController) {
self.view = controller.view
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.message))
tapGesture.numberOfTapsRequired = 2
self.view!.addGestureRecognizer(tapGesture)
self.view!.isUserInteractionEnabled = true
}
#objc func message() {
print("Hey there!")
}
}
Your TapHandler just getting released. Try This:
var tapHandler:TapHandler? = nil
override func viewDidLoad() {
super.viewDidLoad()
tapHandler = TapHandler(controller: self)
}
I have tested the code and is working.
In my NSDocument subclass I instantiate an NSPopover, with .semitransient behaviour, and show it:
popover.show(relativeTo: rect, of: sender, preferredEdge: .maxX)
popover is declared locally. A button method in the popover controller calls:
view.window?.close()
The popover closes, but I have become aware that it remains in memory, deinit() is never called and the NSApp.windows count increases, whereas if I dismiss it by pressing escape or clicking outside it, deinit is called and the windows count doesn't increase.
If I set the window's .isReleasedWhenClosed to true, the windows count doesn't increase, but deinit is still not called.
(Swift 3, Xcode 8)
You have to call performClose (or close) on the popover, not the window.
Thanks -DrummerB for your interest. It has taken me some time to get around to making a simple test application I might send you, and of course it wasn't a document-based one as mine was, and that seemed to be clouding the issue. My way of opening the popover was based on an example I'd recently read, but can't now find or I'd warn people. It went like this:
let popover = NSPopover
let controller = MyPopover(...)! // my convenience init for NSViewController descendant
popover.controller = controller
popover.behaviour = .semitransient // and setting other properties
popover.show(relativeTo: rect, of: sender, preferredEdge: .maxX)
Here's the improved way I've come across:
let controller = MyPopover(...)! // descendant of NSViewController
controller.presentViewController(controller,
asPopoverRelativeTo: rect, of: sender, preferredEdge: .maxX,
behavior: .semitransient) // sender was a NSTable
In the view controller, the 'Done' button's action simply does:
dismissViewController(self)
which never worked before. And now I find the app's windows list doesn't grow, and the controller's deinit happens reliably.
I would suggest doing the following:
Define a protocol like this
protocol PopoverManager {
func dismissPopover(_ sender: Any)
}
In your popoverViewController (in this example we are displaying a filter view controller as a popover) add a variable for the popoverManager like this
/// Filter shown as a NSPopover()
class FilterViewController: NSViewController {
// Delegate
var popoverManager: PopoverManager?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
// Bind this to the close button or action on your popover view controller
#IBAction func closeAction(_ sender: Any) {
self.popoverManager?.dismissPopover(sender)
}
...
}
Now in your viewController that you show the popover from add an extension like this
extension MainViewController: NSPopoverDelegate, PopoverManager {
#IBAction func setFilter(_ sender: AnyObject) {
self.showFilterPopover(sender)
}
func showFilterPopover(_ sender: AnyObject) {
let storyboard = NSStoryboard(name: "Filter", bundle: nil)
guard let controller = storyboard.instantiateController(withIdentifier: "FilterViewController") as? FilterViewController else {
return
}
// Set the delegate to self so we can dismiss the popover from the popover view controller itself.
controller.popoverManager = self
self.popover = NSPopover()
self.popover.delegate = self
self.popover.contentViewController = controller
self.popover.contentSize = controller.view.frame.size
self.popover.behavior = .applicationDefined
self.popover.animates = true
self.popover.show(relativeTo: sender.bounds, of: sender as! NSView, preferredEdge: NSRectEdge.maxY)
}
func dismissPopover(_ sender: Any) {
self.popover?.performClose(sender)
// If you don't want to reuse it
self.popover = nil
}
}
I have a ChatViewController that extends JSQMessagesViewController.
final class ChatViewController: JSQMessagesViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
title = "Messages"
collectionView?.collectionViewLayout.incomingAvatarViewSize = .zero
collectionView?.collectionViewLayout.outgoingAvatarViewSize = .zero
automaticallyScrollsToMostRecentMessage = true
collectionView?.reloadData()
collectionView?.layoutIfNeeded()
collectionView?.collectionViewLayout.springinessEnabled = true
// REMOVE the attachment button for now
self.inputToolbar.contentView?.leftBarButtonItem = nil
self.inputToolbar.contentView?.textView?.placeHolder = "Compose a message to your driver"
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeMessages()
}
private func observeMessages() {
messageRef = channelRef!.child("messages")
let messageQuery = messageRef.queryLimited(toLast:25)
newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) -> Void in
let messageData = snapshot.value as! Dictionary<String, String>
if let id = messageData["senderId"] as String!, let name = messageData["senderName"] as String!, let text = messageData["text"] as String!, text.characters.count > 0 {
self.addMessage(withId: id, name: name, text: text)
self.finishReceivingMessage()
} else {
print("Error! Could not decode message data")
}
})
}
}
This view controller is called by a push segue that has a navigation controller. Within my storyboard, I have a simple segue from a bar item button that pushes to a View Controller with ChatViewController set as the custom class. Within the storyboard, the navigation bar is inferred (shown on top).
However, when I go into ChatViewController, the messages are shown under the navigation bar. Once I click into the message text box, the messages are correctly positioned. How do I fix this so the user does not have to click into the text box? Is there a command I'm missing that correctly lays out the new messages?
This issue has been addressed here and here, actually. You can simply set the top inset to account for the bar's height. Jesse Squires actually made a special member for such cases called topContentAdditionalInset, which you can use like this:
override func viewDidLoad() {
super.viewDidLoad()
self.topContentAdditionalInset = myBarsHeight // in CGFloat
}
//
// ViewController.swift
// FunFacts
//
// Created by Alex Macleod on 4/10/14.
// Copyright (c) 2014 Alex Macleod. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var funFactLabel: UILabel!
#IBOutlet weak var funFactButton: UIButton!
#IBOutlet weak var swipeView: UIView!
// let swipeRec = UISwipeGestureRecognizer()
let factBook = FactBook()
let colorWheel = ColorWheel()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// swipeRec.addTarget(self, action: "swipedView")
// swipeView.addGestureRecognizer(swipeRec)
// swipeView.userInteractionEnabled = true
var swipeRight = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeRight.direction = UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(swipeRight)
var swipeLeft = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(swipeLeft)
var swipeDown = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeDown.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipeDown)
var swipeUp = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeUp.direction = UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(swipeUp)
funFactLabel.text = factBook.randomFact()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.Right:
// swipedAlertViewRight()
self.performSegueWithIdentifier("segueSwipeRight", sender: nil)
case UISwipeGestureRecognizerDirection.Left:
// swipedAlertViewLeft()
swipedLeft()
case UISwipeGestureRecognizerDirection.Down:
var randomColor = colorWheel.randomColor()
view.backgroundColor = randomColor
funFactButton.tintColor = randomColor
funFactLabel.text = factBook.randomFact()
case UISwipeGestureRecognizerDirection.Up:
var randomColor = colorWheel.randomColor()
view.backgroundColor = randomColor
funFactButton.tintColor = randomColor
funFactLabel.text = factBook.randomFact()
default:
break
}
}
}
func swipedLeft() {
self.performSegueWithIdentifier("segueSwipeLeft", sender: nil)
}
// func swipedAlertViewRight(){
// let tapAlert = UIAlertController(title: "Swiped", message: "You just swiped right", preferredStyle: UIAlertControllerStyle.Alert)
// tapAlert.addAction(UIAlertAction(title: "OK", style: .Destructive, handler: nil))
// self.presentViewController(tapAlert, animated: true, completion: nil)
// }
//
// func swipedAlertViewLeft(){
// let tapAlert = UIAlertController(title: "Swiped", message: "You just swiped left", preferredStyle: UIAlertControllerStyle.Alert)
// tapAlert.addAction(UIAlertAction(title: "OK", style: .Destructive, handler: nil))
// self.presentViewController(tapAlert, animated: true, completion: nil)
// }
#IBAction func showFunFact() {
var randomColor = colorWheel.randomColor()
view.backgroundColor = randomColor
funFactButton.tintColor = randomColor
funFactLabel.text = factBook.randomFact()
}
}
So I swipe left and I it takes me to a new viewViewcontroller, I swipe right it takes me to another blank view controller. How do I tell these blank view controllers to segue back to the main view controller?
Do you have a navigation bar in that view controller?
If you do, then you can simply do:
self.navigationController.popViewControllerAnimated(YES)
If you do not, then you simply need to
self.performSegueWithIdentifier("segueSwipeLeft", sender: nil)
(which is to say, perform a segue back to the view controller you came from). Segues are not necessarily a push and pop thing.
Segues create instances of their view controllers. This makes sense when moving in a forward direction, but if you "performSegueWithIdentifier" when moving in a backward direction, it's likely you're not returning to the previous view controller, but rather you're creating and presenting a new instance of the previous view controller.
For example, let's say you two view controllers, A and B, and A has a text field on it that has a value specified by the user. Then you segue to B. Then you use a standard segue back to A. The text won't be in the text field on A because you're looking at a new instance of A, not the original instance of that view controller.
If you want to back-up, there are Unwind segues, which are a special kind of segue to return you to a previous instance. They are rigged up to the green "exit" button at the top of your scene in the storyboard editor. Unwind segues (sometimes called Exit Segues) are interesting because they let you unwind not just to the previous view controller, but all the way back through a deep stack of view controllers, and as part of the unwind they can call different methods on the destination view controller, such as indicating that a Cancel or Save button was tapped on the source view controller.
Programatically, if your view controller was presented modally, you can also use dismissViewController:animated:completion: to back up.