Present viewcontroller from appdelegate over the current view in swift - swift

I am trying to present a view programmatically from the appdelegate. The basic idea it's to present a view so that the user must enter a code to unlock the app. This code works
func applicationWillResignActive(application: UIApplication) {
var storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var vc : PasswordViewController = storyboard.instantiateViewControllerWithIdentifier("PasswordViewController") as PasswordViewController
let navigationController = UINavigationController(rootViewController: vc)
self.window?.rootViewController?.presentViewController(navigationController, animated: true, completion: nil)
}
But have a problem. If the user press home inside another view (like the one shown on the picture) the password viewcontroller does not shows and I get a warning
Warning: Attempt to present UINavigationController: 0x7f8c2263c480 on UITabBarController: 0x7f8c2256ad60 whose view is not in the window hierarchy!
left view presents ok the view, right one not, note that right one is accessed with the + button and it's modal
Any help will be much welcome!

I wrote an app that did something like this - upon resuming the application the user would be prompted for a PIN or return to the login screen.
I think the trick for this is not to try to present a viewController modally - I just overlaid the entire window with a UIView that intercepts all touches and blocks everything behind it.
Ordinarily I just program every subview into a UIView manually. For example:
class PasswordView:UIView{
override init(frame: CGRect) {
super.init(frame: frame)
var disabler = UIView(frame: frame)
disabler.backgroundColor = UIColor(white: 1.0, alpha: 0.2)
self.addSubview(disabler)
var redBar = UIView(frame: CGRectMake(0,22,frame.width,44))
redBar.backgroundColor = UIColor.redColor()
self.addSubview(redBar)
//etc... and add other elements using code
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
This is my preferred means to manage all my views because I maintain the most amount of control, but you might also look into loading from an .xib
At any rate... in my app I created this view in this manner:
var passCode = PasswordView(frame: GCRectZero)
func applicationDidBecomeActive(application: UIApplication) {
passCode.removeFromSuperview()
passCode = PasswordView(frame: self.window!.bounds)
self.window!.addSubview(passCode)
}
I would recommend you give the appDelegate a dedicated var passCode = PasswordView(frame: GCRectZero) so that you can remove it from the superview before adding another. This way you don't stack up a ton of views.

Related

NSTouchBar not releasing last touched view

I am making an app which uses the NSTouchBar.
The touchbar is made by the NSWindowController.makeTouchBar()method.
In this touchbar I can place NSCustomTouchBarItems.
I have made two NSCustomTouchBarItems.
The first one sets a view to a default ui button, with this:
let item001 = NSCustomTouchBarItem(identifier: someIdentifier)
item001.view = NSButton(title: "myButton", target: nil, action: nil)
The second one sets a viewController, with this:
let item002 = NSCustomTouchBarItem(identifier: someIdentifier)
item002.viewController = TestViewController()
The TestViewController only loads a simple view inside its loadView()
method.
class TestViewController: NSViewController {
override func loadView() {
self.view = TestView001(frame: NSRect(x: 0, y: 0, width: 100, height: 30))
}
}
The TestView001 only creates a background color so you can see it.
TestView001 has the following code:
class TestView001: NSView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
print("TestView001.init()")
// Create a background color.
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.green.cgColor
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
All of this works perfectly.
But when I have touched the second item inside the touchbar,
and then close my app's window.
The windowController and everything else is nicely released
from memory.
But I can still see that TestView001 is in memory and not being
released.
When using a standard ui button like in item001, then you don't
have this problem.
It looks like some NSTouch still has a reference to the view
if you look at this image:
However, I do not completely understand this image.
What is the best way of solving this.
Thanks in advance.

Swift, newbie question related to containerView. how to make Storyboard viewcontroller for child the same size as a container view

I've been trying to correctly implement a ContainerView in my practice app after watching and reading various tutorials. I initially tried creating the child by utilizing the storyboard and dragging the ContainerView onto the ViewController in question, and then utilizing the child ViewController that is then automatically created, but I need to have multiple child ViewControllers and I couldn't quite figure that out. I researched some programatic ways to do it and I successfully have it functioning the way I want. The only hiccup being that when I am viewing the child ViewControllers on my storyboard they are full size and do not correlate in size to my ContainerView. So I have to have a bit of trial and error in getting the objects I place in the child to fit in the ContainerView.
Can anyone give me some pointers on how I fix that? I've used the code below. The function runs when a button on the parent ViewController is touched. There are other associated child ViewControllers: child2, child3 that run depending on which button is pushed. I didn't include that extra code below for the sake of being concise.
private lazy var child1: PersonInfoChildView1Controller = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
var viewController = storyboard.instantiateViewController(withIdentifier: "Child1VC") as! PersonInfoChildView1Controller
viewController.person = self.person
addChild(viewController)
return viewController
}()
//MARK: ADD THE CHILD
private func add(asChildViewController viewController: UIViewController) {
containerView.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = containerView.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParent: self)
}
Try to set your child view controller constraints relative to the container view.
Edit your add method like this:
private func add(asChildViewController viewController: UIViewController)
{
viewController.view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(viewController.view)
viewController.view.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
viewController.view.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
viewController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
viewController.view.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
// Notify Child View Controller
viewController.didMove(toParent: self)
}
Also you can create an extension to UIView class to pin the view to the parent view by just one line code
extension UIView {
func pin(to superview: UIView){
translatesAutoresizingMaskIntoConstraints = false
topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
leadingAnchor.constraint(equalTo: superview.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: superview.trailingAnchor).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
}
}
put this in a separate swift file with this method you can make the container view same size as the view controller
containerView.addSubview(viewController.view)
viewController.view.pin(containerView)
This will save you a lot of time in the future.

Avoid boilerplate code to setup UI in viewDidLoad

i am just wondering how did you properly setup your UI in your IOS developments with Swift. Generally, I feel like I need to put a lot of statements in viewDidLoad lifecycle method of a view controller to customize UI elements. I know that I can use storyboard to help to setup those UI elements but sometimes we need to make some adjustments programmatically. Those adjustments resulting in a huge and boilerplate code in viewDidLoad. So, how do you handle this ? Did your use extensions only for the UI part ? Specific classes ? How you can clearly separate UI from logic ?
Make a custom view for it!
If you find yourself writing a lot of this kind of code:
myView.someProperty1 = someValue1
myView.someProperty2 = someValue2
myView.someProperty3 = someValue3
myView.someProperty4 = someValue4
myView.someProperty5 = someValue5
myView.addSubView(subView1)
myView.addSubView(subView2)
myView.addSubView(subView3)
...
and the values that you give the properties are all independent of the view controller, it might be time to create a custom view.
Here is an example:
Create an xib file for your view, and name it the same name as your custom view. You will be adding the subviews of your custom view and all the constraints you need here.
And then you can do something like this:
#INDesignable // add this if you want to see your view drawn on the storyboard!
class MyCustomView: UIView {
#IBOutlet var subView1: UIImageView!
#IBOutlet var subView2: UITextField!
#IBOutlet var subView3: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
let view = viewFromNibForClass()
view.frame = bounds
view.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
addSubview(view)
// set up your view here...
// set all the properties and stuff
}
private func viewFromNibForClass() -> UIView {
let bundle = Bundle(for: MyCustomView.self)
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
}

Where in view lifecycle to update controller after modal UIViewController dismissed

I have a UIViewController with a UILabel that needs to display either "lbs" or "kg". My app has a settings screen (another UIViewController) that is presented modally over the first view controller and the user can select either of the two units and save their preference. If the units are changed and the modal settings screen is dismissed, I of course want the label on the first view controller to be updated with the new units value (but without refreshing the whole view). I thought I knew how to make it work, but evidently I don't.
On my modal settings screen, I have a UISegmentedControl to allow the user to select units. Anytime it's changed, this function updates userDefaults:
func saveUnitsSelection() {
if unitsControl.selectedSegmentIndex == 0 {
UserDefaultsManager.sharedInstance.preferredUnits = Units.pounds.rawValue
} else {
UserDefaultsManager.sharedInstance.preferredUnits = Units.kilograms.rawValue
}
}
Then they would likely dismiss the settings screen. So, I added this to viewDidLoad in my first view controller:
override func viewDidLoad() {
super.viewDidLoad()
let preferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
units.text = preferredUnits
}
That didn't work, so I moved it to viewWillAppear() and that didn't work either. I did some research and some caveman debugging and found out that neither of those functions is called after the view has been loaded/presented the first time. It seems that viewWillAppear will be called a second time if I'm working within a hierarchy of UITableViewControllers managed by a UINavigationController, but isn't called when I dismiss my modal UIViewController to reveal the UIViewController underneath it.
Edit 1:
Here's the view hierarchy I'm working with:
I'm kinda stuck at this point and not sure what to try next.
Edit 2:
The user can tap a 'Done' button in the navigation bar and when they do, the dismissSettings() function dismisses the Settings view:
class SettingsViewController: UITableViewController {
let preferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
// some other variables set here
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.topItem?.title = "Settings"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(self.dismissSettings(_:)))
if preferredUnits == Units.pounds.rawValue {
unitsControl.selectedSegmentIndex = 0
} else {
unitsControl.selectedSegmentIndex = 1
}
}
func dismissSettings(sender: AnyObject?) {
navigationController?.dismissViewControllerAnimated(true, completion: nil)
}
}
THE REAL PROBLEM
You misspelled viewWillAppear. You called it:
func viewWillAppear()
As far as Cocoa Touch is concerned, this is a random irrelevant function that hooks into nothing. You meant:
override func viewWillAppear(animated: Bool)
The full name of the first function is: "viewWillAppear"
The full name of the second function is: "viewWillAppear:animated"
Once you get used to this, the extreme method "overloading" that Cocoa Touch uses gets easier.
This is very different in other languages where you might at least get a warning.
The other lesson that everyone needs to learn when posting a question is: Include All Related Code!
Useful logging function I use instead of print or NSLog, to help find these things:
class Util {
static func log(message: String, sourceAbsolutePath: String = #file, line: Int = #line, function: String = #function, category: String = "General") {
let threadType = NSThread.currentThread().isMainThread ? "main" : "other"
let baseName = (NSURL(fileURLWithPath: sourceAbsolutePath).lastPathComponent! as NSString).stringByDeletingPathExtension ?? "UNKNOWN_FILE"
print("\(NSDate()) \(threadType) \(baseName) \(function)[\(line)]: \(message)")
}
}
[Remaining previous discussion removed as it was incorrect guesses]

How to change content view of Window?

I am developing an mac osx application which have initial window and viewcontroller launched from main storyboard. I want to replace the content view loaded by storyboard with my view.
I am doing this -
func replaceContentView() {
parentViewController = MainViewController(nibName: "MainContainerView", bundle: nil)!
let fullScreenFrame = NSScreen.mainScreen()?.visibleFrame
self.initialWindow.setFrame(fullScreenFrame!, display: false, animate: false)
self.initialWindow.contentView = parentViewController! . view
}
Problem with this approach is that the default viewcontroller is never getting deallocated. deinit() of default viewController is not called at all.
This is causing memory leak. So how to completely remove default content view and associated viewcontroller?
Storyboards don't deal in views, they deal in viewcontrollers. What a Storyboard does when it loads a view into a window is that it creates an NSViewController and then goes
windowController.contentViewController = theViewController
That implicitly also inserts theViewController.view as the window's content view. So do the same, and all will be fine.
Marek's example is wrong, because CustomView shouldn't be an NSView subclass, it should be a CustomViewController class that owns a view containing the label etc. As a bonus, NSViewController will take care of loading the XIB for you as well.
Alternately, you could set windowController.contentViewController = nil (which will remove the old view controller and its content view) and then set your content view. But really, why fight the framework when that's exactly what NSViewController is intended for?
You can write the code in deinit method,may it will help you.
deinit {
// perform the deinitialization
}
Your contentViewController within NSWindow instance still holds strongly its old view. You have replaced just property on your NSWindow instance.
To clarify what you have done:
NSWindow holds strongly against new view
NSViewController holds strongly against old view
You should assign your new view into contentViewController.view property as well
This might be helpfull:
NSWindow.h
/* NSViewController Support */
/* The main content view controller for the window. This provides the contentView of the window. Assigning this value will remove the existing contentView and will make the contentViewController.view the main contentView for the window. The default value is nil. The contentViewController only controls the contentView, and not the title of the window. The window title can easily be bound to the contentViewController with the following: [window bind:NSTitleBinding toObject:contentViewController withKeyPath:#"title" options:nil]. Setting the contentViewController will cause the window to resize based on the current size of the contentViewController. Autolayout should be used to restrict the size of the window. The value of the contentViewController is encoded in the NIB. Directly assigning a contentView will clear out the rootViewController.
*/
#availability(OSX, introduced=10.10)
var contentViewController: NSViewController?
/* The view controller for the window's contentView. Tracks the window property of the same name.
*/
#property (strong) NSViewController *contentViewController NS_AVAILABLE_MAC(10_10);
However what you do seems incorrect if you do this on launch.
You either set custom subclass of contentView to your new nsview subclass which can load it's view from another XIB (no need for storyboard).
Abstract example:
class CustomView: NSView {
#IBOutlet var contentView: NSView!
#IBOutlet weak var label: NSTextField!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initSubviews()
}
func initSubviews() {
let nib = NSNib(nibName: "CustomView", bundle: nil)
nib.instantiateWithOwner(self, topLevelObjects: nil)
contentView.frame = bounds
addSubview(contentView)
}
}
PS: topLevelObjects is set to nil because you hold strongly contentView. So no need to worry about memory management.