Switch tab bar programmatically in Swift - xcode

I have a tab bar application and i have a button on my first view which i want to when pressed switch to my second tab programmatically in the tab bar.
I can't quite seem to figure it out how to get the index etc to switch to it i've tried stuff like this.
tababarController.selectedIndex = 1
With no success.

Thats pretty simple tabBarController is declared as an optional type
var tabBarController: UITabBarController? { get }
The nearest ancestor in the view controller hierarchy that is a tab bar
controller. If the view controller or one of its ancestors is a child
of a tab bar controller, this property contains the owning tab bar
controller. This property is nil if the view controller is not
embedded inside a tab bar controller.
So you just need to add "?" at the end of it:
#IBAction func goToSecond(_ sender: Any) {
tabBarController?.selectedIndex = 1
}

Swift 3:
func switchToDataTab() {
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(switchToDataTabCont), userInfo: nil, repeats: false)
}
func switchToDataTabCont(){
tabBarController!.selectedIndex = 0
}
Swift 4+:
func switchToDataTab() {
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(switchToDataTabCont), userInfo: nil, repeats: false)
}
#objc func switchToDataTabCont(){
tabBarController!.selectedIndex = 0
}

The solution provided by Leo Dabus (see above) works fine for me. However - some controls have bad states. Can't fix that, but this little workaround will do you all good:
func switchToDataTab(){
NSTimer.scheduledTimerWithTimeInterval(0.2,
target: self,
selector: "switchToDataTabCont",
userInfo: nil,
repeats: false)
}
func switchToDataTabCont(){
tabBarController!.selectedIndex = 0
}

Add to Anthony's code:
func switchToDataTab(){
NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: #selector(switchToDataTabCont), userInfo: nil,repeats: false)
}
func switchToDataTabCont(){
tabBarController!.selectedIndex = 0
}
Where the selector class has been changed to
#selector(switchToDataTabCont)

Related

Obtaining a View Controller Reference

I read quite a few questions and answers no this problem. Some are for Ojective C. Some are for iOS. The ones that were close to what I need didn't work.
I've set up a protocol for delegation. It doesn't work. The problem is that delegate variable isn't set. I need the reference to an active controller.
Delegator
protocol SwitchTabDelegate: class {
func selectTab(tab: Int)
}
class ViewController: NSViewController {
weak var delegate: SwitchTabDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func selectCompositions(_ sender: NSButton) {
if let delegate = self.delegate {
delegate.selectTab(tab: 2)
}
else {
print("self.delegate is nil")
}
print("delegate called")
}
}
Delegatee
class TabViewController: NSTabViewController, SwitchTabDelegate {
var viewController : ViewController?;
override func viewDidLoad() {
super.viewDidLoad()
//viewController = storyboard?.instantiateController(withIdentifier: "viewController") as? ViewController
// viewController?.delegate = self
// print(viewController)
}
func selectTab(tab: Int) {
print("In the delegate")
switchToDataTab()
}
func switchToDataTab() {
Timer.scheduledTimer(timeInterval: 0.2, target: self,
selector: #selector(switchToDataTabCont),
userInfo: nil, repeats: false)
}
func switchToDataTabCont(){
self.selectedTabViewItemIndex = 2
}
}
The delegatee is the main NSViewContoller. On the storyboard, it contains two buttons and a Container view controller. Embedded in the container view controller is the TabViewController, the delegatee. You can see in the delegatee where I tried to get a reference. It does get a reference, presumably to the newly instantiated instance. I need a reference to the original view controller that was spun up when the application started.
Answer
I added the following code to the delegator:
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
let controller = segue.destinationController as! TabViewController
self.delegate = controller as SwitchTabDelegate
}
That's not how it should work following the design pattern. The delegator should have no knowledge of the delegatee. I've spent way too much time on this issue so a hack is going to do.
When using storyboards, you want to "push" references to children when they are created vs. pulling them from an upstream controller. This is what -prepareForSegue:sender: is used for.

Swift 3 - How to use enum raw value as NSNotification.Name?

I'm using Xcode 8 beta 5 and I'm trying to setup an enum of notifications like this
enum Notes: String {
case note1
case note2
}
Then trying to use them as the notification names
NotificationCenter.default.post(name: Notes.note1.rawValue as NSNotification.Name,
object: nil, userInfo: userInfo)
But I'm getting an error.
Cannot convert value of type 'String' to specified type 'NSNotification.Name'
Is there a work around, or am I missing something? It works in Xcode 7.3.1
Any help would be appreciated.
Here you go, Use Swift 3 & Xcode 8.0
enum Notes: String {
case note1 = "note1"
case note2 = "note2"
var notification : Notification.Name {
return Notification.Name(rawValue: self.rawValue )
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.post(name: Notes.note2.notification ,object: nil, userInfo: nil)
}
}
Another way
import UIKit
extension Notification.Name
{
enum MyNames
{
static let Hello = Notification.Name(rawValue: "HelloThere")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.post(name: Notification.Name.MyNames.Hello ,object: nil, userInfo: nil)
}
}
I am doing this way, For me this is more simple way to manage Notification names.
Swift 3.0 and Xcode 8.0
Using extension of Notification.Name, we can define static names inside that as following.
extension Notification.Name {
static let newPasscodeSet = Notification.Name("newPasscodeSet")
static let userLoggedIn = Notification.Name("userLoggedIn")
static let notification3 = Notification.Name("notification3")
}
We can use that names like this:
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(self.newPasscodeSetAction), name: .newPasscodeSet, object: nil)
}
func newPasscodeSetAction() {
// Code Here.
}
Hope this simple way helpful for you.
As far as I know, there was no type NSNotification.Name in Swift 2.2.1/SDKs bundled in Xcode 7.3.1, so I'm curious how you have made it work.
Anyway you need to write something like this if you want to utilize your enum:
NotificationCenter.default.post(name: NSNotification.Name(Notes.note1.rawValue),
object: nil, userInfo: userInfo)
By the way, my best recommendation to define your own Notification.Name is using extension which defines static properties:
extension Notification.Name {
static let note1 = NSNotification.Name("note1")
static let note2 = NSNotification.Name("note2")
}
(It's a little bit longer than enum..., but) you can use it like this:
NotificationCenter.default.post(name: .note1,
object: nil, userInfo: userInfo)
Maybe another approach in swift 4.2
extension Notification.Name{
struct RecordListNotification {
static let recordListDidChange:Notification.Name = Notification.Name("recordListDidChange")
static let recordListTimeDidChange = Notification.Name("recordListTimeDidChange")
}
}
and then
NotificationCenter.default.post(name: Notification.Name.RecordListNotification.recordListTimeDidChange, object: nil)
also to avoid verbose:
typealias RecordListNotification = Notification.Name.RecordListNotification
And it can be used:
NotificationCenter.default.post(name: RecordListNotification.recordListTimeDidChange, object: nil)

Changing NSCursor for NSView above an NSTextView

Found a similar question to mine(this),
but my issues seems to be a bit more associated with view hierarchy.
I have a NSTextView, then as sibling views, several other NSViews on top of it.
Similar to the question linked above, I setup a tracking area, and applied the cursor as such:
class CursorChangingView: NSView {
override func updateTrackingAreas() {
let trackingArea = NSTrackingArea(rect:
}
override func cursorUpdate(event: NSEvent) {
NSCursor.arrowCursor().set()
}
}
It does seem to work when hovering, but immediately goes back to the IBeam Cursor, which is the default cursor for NSTextViews under this CursorChangingView.
Is this the proper way of applying changing the cursor when hovering over a certain NSView, and is the NSTextView under it overriding my overrriding?
All you need is to subclass a custom view, override awakeFromNib method, add the custom tracking area for [.mouseMoved, .activeAlways] events: NSTrackingArea Info there. There is no need to override resetCursorRects and/or updateTrackingAreas. All you need is to override mouseMoved method and set the desired cursor there:
Note about discardCursorRects method:
From the docs
You need never invoke this method directly
Xcode 9 • Swift 4
import Cocoa
class CursorChangingView: NSView {
override func awakeFromNib() {
addTrackingArea(NSTrackingArea(rect: bounds, options: [.activeAlways, .mouseMoved], owner: self, userInfo: nil))
wantsLayer = true
layer?.backgroundColor = NSColor.cyan.cgColor
layer?.borderColor = NSColor.black.cgColor
layer?.borderWidth = 1
}
#objc override func mouseMoved(with theEvent: NSEvent) {
NSCursor.pointingHand.set()
}
}
Sample
Thanks #Leo Dabus for your answer,
but I managed to solve it, so I will post my answer too.
In my case, for some reason, mouseEntered and mouseEntered did not work at all.
So here is my code that finally got it to work:
class CursorChangingView: NSView {
let trackingArea: NSTrackingArea?
func setupTracking() {
if self.trackingArea == nil {
self.trackingArea = NSTrackingArea(rect: self.bounds, options: NSTrackingAreaOptions.ActiveAlways | NSTrackingAreaOptions.MouseMoved | NSTrackingAreaOptions.CursorUpdate | NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveInActiveApp, owner: self, userInfo: nil)
self.addTrackingArea(self.trackingArea!)
}
}
override func updateTrackingAreas() {
self.trackingArea = NSTrackingArea(rect: self.bounds, options: NSTrackingAreaOptions.ActiveAlways | NSTrackingAreaOptions.CursorUpdate | NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveInActiveApp, owner: self, userInfo: nil)
self.addTrackingArea(self.trackingArea!)
}
override func resetCursorRects() {
self.discardCursorRects()
self.addCursorRect(self.bounds, cursor: NSCursor.arrowCursor())
}
override func mouseMoved(theEvent: NSEvent) {
NSCursor.arrowCursor().set()
}
}
It might be a little excessive, but worked, so will share this as my own solution.
A few important notes:
Be careful calling super on your mouseMoved or similar events, or the cursor might just get reset by the base class implementation.
Only reset your tracking area when the parent view size changes; if you try to do this by overriding layout() it's going to be happening all the time which is not great
Here's an example class that you can just use as a base class in your storyboards.
Swift 4 code:
import Cocoa
final class MouseTrackingTextView: NSTextView {
// MARK: - Lifecycle
override func awakeFromNib() {
setupTrackingArea()
}
// MARK: - Resizing
// Call this in your controller's `viewDidLayout`
// so it only gets called when the view resizes
func superviewResized() {
resetTrackingArea()
}
// MARK: - Mouse Events
override func resetCursorRects() {
addCursorRect(bounds, cursor: cursorType)
}
override func mouseMoved(with event: NSEvent) {
cursorType.set()
}
// MARK: - Private Properties
private var currentTrackingArea: NSTrackingArea?
private var cursorType: NSCursor {
return isEditable ? .iBeam : .pointingHand
}
// MARK: - Private API
private func setupTrackingArea() {
let trackingArea = NSTrackingArea(rect: bounds,
options: [.activeAlways, .mouseMoved],
owner: self, userInfo: nil)
currentTrackingArea = trackingArea
addTrackingArea(trackingArea)
}
private func resetTrackingArea() {
if let trackingArea = currentTrackingArea {
removeTrackingArea(trackingArea)
}
setupTrackingArea()
}
}

how to close NSAlert after some time if no button is clicked by user

I want to close a alert Window (NSAlert) after specific period of time if user does not click on any of the buttons. Is there a way to set up a NSTimer to close the Alert Panel after some time, say 30 sec.
I just done something like that. I revised some of my codes for you here:
class ViewController: NSViewController {
var progressWindow:NSAlert!
func testOfProgressWindow() {
progressWindow = NSAlert()
progressWindow.alertStyle = .InformationalAlertStyle
progressWindow.messageText = "some notes"
let date = NSDate(timeInterval: 30, sinceDate: NSDate())
let uiUpdateTimer = NSTimer(fireDate: date, interval: 0, target: self, selector: "closeSheet", userInfo: nil, repeats: false)
let runLoop = NSRunLoop.mainRunLoop()
runLoop.addTimer(uiUpdateTimer, forMode: NSDefaultRunLoopMode)
progressWindow.beginSheetModalForWindow(self.view.window!, completionHandler: nil)
}
func closeSheet() {
self.view.window?.endSheet(self.progressWindow.window)
}
}

ViewController.Type does not have a member named

Just a simple task, but I'm in trouble. Trying to make a different way but it fails.
How to init NSTimer with declared previously variable? Neither var nor let helps.
The initial value of a property (in your case: timer) cannot depend on another property of the class (in your case: interval).
Therefore you have to move the assigment timer = NSTimer(interval, ...) into a method of the
class, e.g. into viewDidLoad. As a consequence, timer has to be defined as an
optional or implicitly unwrapped optional.
Note also that Selector(...) takes a literal string as argument, not the method itself.
So this should work:
class ViewController: UIViewController {
var interval : NSTimeInterval = 1.0
var timer : NSTimer!
func timerRedraw() {
}
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer(timeInterval: interval, target: self, selector: Selector("timerRedraw"), userInfo: nil, repeats: true)
// ...
}
// Other methods ...
}
Try:
var interval:NSTimeInterval = 1.0
var timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "timerRedraw:", userInfo: nil, repeats: true)
pro-tip and hopefully an appreciated FYI: Swift functions should also start with lower case letters (i.e. "timerRedraw").

Resources