Initialize a subclass of NSViewController without a xib file - swift

I am currently writing an OS X application using Swift 2. I am wanting to build the UI without XIBs or Storyboards. The problem I am having is on initializing a custom ViewController that I can put my views in.
Here is my AppDelegate:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var viewController: MyViewController?
func applicationDidFinishLaunching(aNotification: NSNotification) {
viewController = MyViewController()
self.window.contentView!.addSubview(viewController!.view)
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
And MyViewController:
class MyViewController: NSViewController {
var textField: NSTextField?
override func viewDidLoad() {
super.viewDidLoad()
textField = NSTextField(frame: NSRect(x: 10, y: 10, width: 100, height: 100))
textField!.bezeled = false
textField!.drawsBackground = false
textField!.editable = false
textField!.selectable = false
textField!.stringValue = "TEST"
self.view.addSubview(textField!)
}
}
The problem is that when I add the viewController's view as a subview of the window's contentView, I get the following error and the view doesn't load.
2015-12-06 17:34:18.204 Test[9682:1871784] -[NSNib
_initWithNibNamed:bundle:options:] could not load the nibName:
Test.MyViewController in bundle (null).
I'm not sure what I'm doing wrong - any help would be appreciated.

From the NSViewController documentation:
If you pass in a nil for nibNameOrNil then nibName will return nil and
loadView will throw an exception; in this case you must invoke
setView: before view is invoked, or override loadView.
The initializer for MyViewController() uses nil for the nibName.
Two potential fixes:
1. Set the view in your AppDelegate
func applicationDidFinishLaunching(aNotification: NSNotification) {
viewController = MyViewController()
viewController!.view = NSView() // added this line; edit to set any view of your choice
self.window.contentView!.addSubview(viewController!.view)
}
Alternately,
2. Override loadView in your subclassed ViewController
import Cocoa
class MyViewController: NSViewController {
var textField: NSTextField?
override func loadView() {
self.view = NSView() // any view of your choice
}
override func viewDidLoad() {
super.viewDidLoad()
textField = NSTextField(frame: NSRect(x: 10, y: 10, width: 100, height: 100))
textField!.bezeled = false
textField!.drawsBackground = false
textField!.editable = false
textField!.selectable = false
textField!.stringValue = "TEST"
self.view.addSubview(textField!)
}
}

Related

How to use “Look Up in Dictionary” in NSPopover Swift

I'm building my macOS app that contains NSPopover with NSTextView inside. If "Look up & data detectors" is active at system preferences, NSTextView will support this functionality without any additional settings. But in case, when you place NSTextView inside of NSPopover, this function won't work anymore. Is there any way to fix this problem without NSTextView.showDefenition(for: at:)?
How it usually works
Same thing, but won't work with NSTextView that placed inside of NSPopover
I've started new test project just to check behaviour, so code is simple as it is.
Main Controller:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var popoverButton: NSButton!
#IBAction func pressButton(_ sender: NSButton) {
let popover = NSPopover()
popover.contentViewController = PopoverController()
popover.contentSize = CGSize(width: 470, height: 300)
popover.show(relativeTo: popoverButton.bounds, of: popoverButton, preferredEdge: NSRectEdge.minY)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Popover Controller:
import Cocoa
class PopoverController: NSViewController {
var textView = NSTextView()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear() {
super.viewWillAppear()
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.frame = view.bounds
}
}

Why could not cast value of type 'UIView' to other View?

This question deals with the same code with my previous question.
(The previous question is Why is this View nil in awakeFromNib()?)
But totally different approach.
It's not the duplicated question.
So let me restructure my question.
First, let me summarize my project.
Thanks to previous answer, I can reuse xib now.
This is CardContentView.xib
Codes are below
#IBDesignable class CardContentView: UIView {
#IBOutlet weak var backgroundView: UIView!
#IBInspectable var nibName:String?
var contentView: UIView?
override func awakeFromNib() {
super.awakeFromNib()
xibSetup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
contentView?.prepareForInterfaceBuilder()
}
func xibSetup(){
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
guard let nibName = nibName else { return nil }
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
And this is CardView.xib, the property follows 'class CardContentView'
Codes are below
class CardView: UIView {
#IBOutlet weak var cardContentView: CardContentView!
override func awakeFromNib() {
cardContentView.layer.cornerRadius = 16
}
}
This is Main.storyboard. with a single UIView.
Codes are below
class ViewController: UIViewController {
#IBOutlet weak var cardView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
But, I want to reuse class 'CardView' to 'cardView' property in ViewController.
I want to get 'background' property to 'cardView' property (In simple words, I want to change the yellow box to the blue box in CardContentView!).
And I want to make 'cardView' property with a transition effect.
But, I cannot make extension because it's not in a UIView class, but in a ViewController class.
So I need to make another UIView class.
That's why I made a CardView class separately.
(Of course, I want to organize codes neatly by making another file.)
So, I tried 2 different ways.
Set the property 'cardView' class as CardView, and connect it to ViewController.
class ViewController: UIViewController {
#IBOutlet weak var cardView: CardView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Cast 'CardView' class to other property in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let tappedView = self.cardView as! CardView
self.view.addSubview(tappedView)
}
But both of them make errors.
1. When I made '#IBOutlet weak var cardView: CardView!', it throws error message, 'Unexpectedly found nil while unwrapping an Optional value, cardContentView = nil'.
2. When I made 'let tappedView = self.cardView as! CardView', it throws error message, 'Could not cast value of type 'UIView' (0x10a229ff8) to 'AwakeFromNibTutorial.CardView' (0x1060aa8d0).'
How can I solve this problem?
Please help me!
In the CardView class's awakeFromNib(), you are not calling super.awakeFromNib().
You would have to use the same logic (xibSetup()) that you used to load the CardContentView from it's corresponding XIB, to load the CardView also from its XIB.
In the ViewController in your storyboard, have you assigned the class of the UIView to CardView?

Set CustomView delegate before init

Although I could create a func setImages () in my CustomView class and call this after initializing myCustomView, I would like to understand if there's a cleaner way to set a view's delegate so that it is accessible when being initialized.
My main ViewController contains
class Main: UIViewController, CustomViewDelegate {
var imagesArray:[UIImage] = [Image1,Image2,Image3,Image4,Image5]
var myCustomView = CustomView()
override func viewDidLoad() {
super.viewDidLoad()
myCustomView.delegate = self
myCustomView = CustomView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
//this causes init of CustomView, but delegate is now nil and button images don't load
}
}
My CustomView file contains
var buttonsArray = [Button1,Button2,Button3,Button4,Button5]
override init(frame: CGRect) {
super.init(frame : frame)
for n in 0..< buttonsArray.count {
buttonsArray[n].setImage(delegate?.imagesArray[n], for: .normal)
}
}
You can create a new initializer which takes a frame and a delegate type and set the delegate before you set images to buttons
init(frame: CGRect,sender: CustomViewDelegate) {
super.init(frame : frame)
self.delegate = sender
for n in 0..< buttonsArray.count {
buttonsArray[n].setImage(delegate?.imagesArray[n], for: .normal)
}
}
For that, you have to confirm your viewController for the delegate(obviously).Call your customView like this in viewController:
class ViewController: UIViewController, CustomViewDelegate {
var myCustomView: CustomView!
override func viewDidLoad() {
myCustomView = CustomView(frame: self.view.bounds, sender: self)
}
}
hope it helps!

Swift 4: scrollViewDidScroll not working

I created first View controller "MainViewController" and put into it Container View with outlet contentContainer.
I created second View Controller "AboutContentViewController" and put into it ScrollView
I connect Container View and AboutContentViewController programmatically:
class MainViewController: UIViewController {
#IBOutlet weak var contentContainer: UIView!
override func viewDidLoad() {
let contentControllerName = "AboutContentViewController"
if let contentController = storyboard?.instantiateViewController(withIdentifier: contentControllerName) {
contentContainer.addSubview(contentController.view)
}
}
}
I tried to catch scrollViewDidScroll event using this code
class AboutContentViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var topGalleryScrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
topGalleryScrollView.contentSize = CGSize(width: 3750.0, height: 417.0)
topGalleryScrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrollViewDidScroll called")
}
}
In this context scrollViewDidScroll event not working
I have solved problem. AboutContentViewController is need to be add as child view controller to MainVewController before adding its's view to container:
class MainViewController: UIViewController {
#IBOutlet weak var contentContainer: UIView!
override func viewDidLoad() {
let contentControllerName = "AboutContentViewController"
if let contentController = storyboard?.instantiateViewController(withIdentifier: contentControllerName) {
addChildViewController(contentController)
contentContainer.addSubview(contentController.view)
}
}
}

How to modified object of child viewcontroller from parent viewcontroller from in swift

I want to modify an object of child UIViewController but it is not working. Can anyone solve my problem? thanks a lot ~~
below is my code
My ViewController class:
import UIKit
class ViewController: UIViewController {
var x:UIView?
override func viewDidLoad() {
super.viewDidLoad()
var sec = second()
sec.x?.backgroundColor = UIColor.redColor()
view.addSubview(sec.view)
self.addChildViewController(sec)
}
}
My second class:
class second:UIViewController{
var x:UIView!
override func viewDidLoad() {
super.viewDidLoad()
x = UIView(frame: CGRectMake(55, 55, 100, 100))
x!.backgroundColor = UIColor.greenColor()
self.view.addSubview(x!)
}
}
This is happening because the view is probably nil when you are attempting to set its background color. Just add a background color property inside the second view controller and set that. Then inside viewDidLoad set the second view controller's background color from there.
Something like this
class ViewController: UIViewController {
var x:UIView?
override func viewDidLoad() {
super.viewDidLoad()
var sec = second()
sec.backColor = UIColor.redColor()
view.addSubview(sec.view)
self.addChildViewController(sec)
}
}
And then:
class second:UIViewController {
var backColor = UIColor.greenColor()
var x:UIView!
override func viewDidLoad() {
super.viewDidLoad()
x = UIView(frame: CGRectMake(55, 55, 100, 100))
x.backgroundColor = backColor
self.view.addSubview(x)
}
}
After initializing second view controller, call addChildViewController(instanceOfSecondVC)
And after adding it as subview, call sec.didMoveToParentViewController(self).
So your code will be like:
import UIKit
class ViewController: UIViewController {
var x:UIView?
override func viewDidLoad() {
super.viewDidLoad()
var sec = second()
addChildViewController(sec)
sec.x?.backgroundColor = UIColor.redColor()
view.addSubview(sec.view)
self.addChildViewController(sec)
sec.didMoveToParentViewController(self)
}
}