macOS - Swift 4.0 Container View does not show NSView - swift

I'm writing my first macOS App with Swift 4.0 and
currently I'm struggling with my Container View.
I've this layout
Layout while running the app
The issue is, when I click on one of the TableView Items my Container View does not load the NSView with the "THEMA 1" label in it.
(All TableView Items should load the "THEMA 1" View)
Here is my ViewController code
import Cocoa
class ViewController: NSViewController {
//VARS
var tableViewArray = ["Thema 1", "Thema 2", "Thema 3", "Thema 4", "Thema 5"]
var containerViewArray: [NSView]!
var thema1View = Thema1()
//IBOutlets
#IBOutlet weak var tableView: NSTableView!
#IBOutlet weak var container: NSView!
override func viewDidLoad() {
super.viewDidLoad()
//setup TableView
tableView.delegate = self
tableView.dataSource = self
tableView.action = #selector(onItemClicked)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
//Extensions
extension ViewController: NSTableViewDelegate, NSTableViewDataSource {
//show what is clicked on tableView
#objc private func onItemClicked() {
if (tableView.clickedRow <= tableViewArray.count - 1) && (tableView.clickedRow >= 0) {
print("\(tableViewArray[tableView.clickedRow]), clicked")
removeAllSubView()
container.addSubview(thema1View, positioned: .above, relativeTo: container)
}
else {
print("There is no content in this cell")
}
}
//removes all SubViews from containerView
func removeAllSubView() {
for view in container.subviews{
view.removeFromSuperview()
}
}
//rows in tableViewArray
func numberOfRows(in tableView: NSTableView) -> Int {
return tableViewArray.count
}
//populate the tableView
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return tableViewArray[row]
}
}
Thema1 ViewController code
import Cocoa
class Thema1: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
}
I think the issue is buried in the onItemClicked() method, but as I mentioned I'm quite new to macOS and Swift 4.0.
Therefore I've found quite many results regarding iOS but a few macOS, but nothing hinting to my case.
Thank you for taking your time and best regards,
Danny

I found sample code SourceView but it is rather complicated and in Objective-C. Here's my translation of the relevant part to Swift (I'm not very familiar with Swift).
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var tableViewArray = ["Thema 1", "Thema 2", "Thema 3", "Thema 4", "Thema 5"]
var detailViewController: NSViewController?
#IBOutlet weak var container: NSView!
#IBAction func tableViewClickAction(_ tableView: NSTableView) {
if tableView.clickedRow >= 0 {
print("\(tableViewArray[tableView.clickedRow]), clicked")
// remove the detail view if there is one
if let oldViewController = detailViewController {
oldViewController.view.removeFromSuperview()
oldViewController.removeFromParentViewController()
detailViewController = nil
}
// determine the identifier of the new detail view, this is the Storyboard ID of the detail scene
var sceneIdentifier: NSStoryboard.SceneIdentifier?
switch tableView.clickedRow {
case 0:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema1")
case 1:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema2")
case 2:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema3")
case 3:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema4")
case 4:
sceneIdentifier = NSStoryboard.SceneIdentifier(rawValue: "Thema5")
default:
sceneIdentifier = nil
}
if sceneIdentifier != nil {
// load the new detail controller and view from the storyboard
detailViewController = self.storyboard?.instantiateController(withIdentifier:sceneIdentifier!) as? NSViewController
if let newViewController = detailViewController {
newViewController.view.translatesAutoresizingMaskIntoConstraints = false
// add controller and view
self.addChildViewController(newViewController)
container.addSubview(newViewController.view)
// add constraints
let views = ["targetView": newViewController.view]
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[targetView]|",
options: [], metrics: nil, views: views)
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[targetView]|",
options: [], metrics: nil, views: views)
NSLayoutConstraint.activate(horizontalConstraints)
NSLayoutConstraint.activate(verticalConstraints)
}
}
}
else {
print("Clicked outside the cells")
}
}
}

Thank your for helping me out with the storyboard idea.
I've made a test dummy, where I had my Main view and made two ViewControllers with labels in it.
All ViewControllers got their Storyboard-IDs.
The Main view had two buttons, both assigned to one of the ViewControllers.
By clicking them, you will load one of the Test ViewControllers.
I came up with this
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func switchViews(viewNumber: Int) {
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
var controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "Main"))
switch viewNumber {
case 1:
print("View: 1")
controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "Test1"))
case 2:
print("View: 2")
controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "Test2"))
default:
print("Default");
}
if let window = NSApplication.shared.mainWindow {
window.contentViewController = controller as? NSViewController
}
}
#IBAction func buttonAction(_ sender: NSButton) {
var viewNumber: Int = 0
switch sender.title {
case "controller1":
viewNumber = 1
print("Button: 1")
case "controller2":
viewNumber = 2
print("Button2: 2")
default:
print("Default")
}
switchViews(viewNumber: viewNumber)
}
}

Related

Table Content disappears on Scroll in TableView with Custom Cell using Subview - Swift

I have a ViewController which uses multiple Subviews (HomeViewController, etc.) which can be selected via a Custom Tab Bar at the bottom of my app. Inside the HomeViewController there is a UIView containing a UITableView containing a Prototype Custom Cell with name and image.
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var friendView: UITableView!
let friends = ["batman", "harsh", "ava", "sasha", "fatima", "alfred"]
override func viewDidLoad() {
super.viewDidLoad()
friendView.delegate = self
friendView.dataSource = self
friendView.allowsSelection = false
}
}
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friends.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = friendView.dequeueReusableCell(withIdentifier: "customCell") as! CustomCell
let friend = friends[indexPath.row]
cell.avatarImg.image = UIImage(named: friend)
cell.nameLbl.text = friend
return cell
}
}
Custom cell:
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet weak var friendView: UIView!
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var avatarImg: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
When I start the app, everything looks just fine. However, when I start scrolling inside the table, all data suddenly disappears. All relations between storyboard and code should be just fine. I think it might have got something to do with my need of using a Subview.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tabBarView: UIView!
#IBOutlet weak var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
Design.makeCornersRound(view: tabBarView, radius: 10.0)
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
self.switchToHomeViewController()
}
}
#IBAction func onClickTabBar(_ sender: UIButton) {
let tag = sender.tag
if tag == 1 {
switchToIncomingsViewController()
}
else if tag == 2 {
switchToSpendingsViewController()
}
else if tag == 3 {
switchToHomeViewController()
}
else if tag == 4 {
switchToSavingsViewController()
}
else if tag == 5 {
switchToSettingsViewController()
}
}
func switchToHomeViewController() {
guard let Home = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
contentView.addSubview(Home.view)
Home.didMove(toParent: self)
}
...
}
Reference to the tutorial I have been trying to implement: https://www.youtube.com/watch?v=ON3Z0PXSoVk
In this function:
func switchToHomeViewController() {
// 1
guard let Home = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
// 2
contentView.addSubview(Home.view)
// 3
Home.didMove(toParent: self)
// 4
}
At 1 you create an instance of HomeViewController
at 2 you add its view to cotentView
at 3 you call didMove() ... but that doesn't do anything because you haven't added the controller to your hierarchy
at 4 your Home instance goes away, so the code in that controller no longer exists
You need to add the controller as a child controller.
As a side note, use lowerCase for variable names:
func switchToHomeViewController() {
// create an instance of HomeViewController
guard let homeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
// add it as a child view controller
self.addChild(homeVC)
// add its view
contentView.addSubview(homeVC.view)
// here you should either set the view's frame or add constraints
// such as:
homeVC.view.frame = contentView.bounds
// inform the controller that it moved to a parent controller
homeVC.didMove(toParent: self)
}

How to make Horizontal scroll segment in swift

when we scroll segment horizontally then the segment and regarding viewcontroller should scroll move horizontally
for that i have tried SJSegmentedScrollView
i have installed cocoapod and added below code:
code: but here even segment not coming.. where i am mistake.. how to add segment and viewcontrollers horizontally
or is there any other thirdparty library for segment please do help here
import UIKit
import SJSegmentedScrollView
class SegmentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationController?.isNavigationBarHidden = true
if let storyboard = self.storyboard {
let headerViewController = storyboard
.instantiateViewController(withIdentifier: "SegmentViewController")
let firstViewController = storyboard
.instantiateViewController(withIdentifier: "FirstTableViewController")
firstViewController.title = "First"
let secondViewController = storyboard
.instantiateViewController(withIdentifier: "SecondTableViewController")
secondViewController.title = "Second"
let thirdViewController = storyboard
.instantiateViewController(withIdentifier: "ThirdTableViewController")
thirdViewController.title = "Third"
self.present(thirdViewController, animated: false, completion: nil)
let segmentedViewController = SJSegmentedViewController(headerViewController: headerViewController,
segmentControllers: [firstViewController,
secondViewController,
thirdViewController])
addChild(segmentedViewController)
self.view.addSubview(segmentedViewController.view)
segmentedViewController.view.frame = self.view.bounds
segmentedViewController.didMove(toParent: self)
}
}
}
I think no need to the using third party frameworks for every development. Try native ways first. For example you can do something like this by using UIKit, UISegmentedControl Apple Document
class ViewController: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentedControlPressed(_ sender: UISegmentedControl) {
switch segmentedControl.selectedSegmentIndex {
case 0: textLabel.text = "First";
case 1: textLabel.text = "Second";
case 2: textLabel.text = "Third";
default: break;
}
}
}

Beginner question on passing data between view controllers

I am trying to recreate the Notes app in iOS. I have created an initial View Controller which is just a table view. A user can go to a Detail View Controller to compose a new note with a Title and Body section. When they click Done, I want to manipulate the tableView with note's details.
I am struggling saving the details of what the user entered to use on my initial view controller.
Here's my Notes class which defines the notes data:
class Notes: Codable {
var titleText: String?
var bodyText: String?
}
Here is the Detail View controller where a user can input Note details:
class DetailViewController: UIViewController {
#IBOutlet var noteTitle: UITextField!
#IBOutlet var noteBody: UITextView!
var noteDetails: Notes?
var noteArray = [Notes]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(updateNote))
noteTitle.borderStyle = .none
}
#objc func updateNote() {
noteDetails?.titleText = noteTitle.text
noteDetails?.bodyText = noteBody.text
noteArray.append(noteDetails!) // This is nil
// not sure if this is the right way to send the details over
// let vc = ViewController()
// vc.noteArray.append(noteDetails!)
if let vc = storyboard?.instantiateViewController(identifier: "Main") {
navigationController?.pushViewController(vc, animated: true)
}
}
}
I also have an array on my initial view controller as well. I think I need this one to store note data to display in the tableView (and maybe don't need the one on my Detail View controller?). The tableView is obviously not completely implemented yet.
class ViewController: UITableViewController {
var noteArray = [Notes]()
override func viewDidLoad() {
super.viewDidLoad()
print(noteArray)
self.navigationItem.setHidesBackButton(true, animated: true)
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composeNote))
}
#objc func composeNote() {
if let dvc = storyboard?.instantiateViewController(identifier: "Detail") as? DetailViewController {
navigationController?.pushViewController(dvc, animated: true)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
noteArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
return cell
}
Just using Delegate:
First create delegate protocol with a func to send back note to your viewController
protocol DetailViewControllerDelegate: AnyObject {
func newNoteDidAdded(_ newNote: Note)
}
Next add the delegate variable to DetailViewController, and call func noteDataDidUpdate to send data back to viewController
class DetailViewController: UIViewController {
weak var delegate: DetailViewControllerDelegate?
#objc func updateNote() {
....
delegate?.newNoteDidAdded(newNote)
}
}
finally, set delegate variable to viewController and implement this in ViewController
class ViewController: UIViewController {
....
#objc func composeNote() {
if let dvc = storyboard?.instantiateViewController(identifier: "Detail") as? DetailViewController {
dvc.delegate = self
navigationController?.pushViewController(dvc, animated: true)
}
}
}
extension ViewController: DetailViewControllerDelegate {
func newNoteDidAdded(_ newNote: Note) {
// do some thing with your new note
}
}

Swift: NSTextfield and popover with suggested list of tags

I'm trying to do something like this:
This is what I have now:
How I'm doing this now:
I have MainViewController with input field: NSTextField.
Using controlTextDidChange I'm checking if input field contains # character.
If yes - I'm opening popover using PopoverViewController.
In the PopoverViewController I have NSTableView that displays a list of available tags.
By clicking on the row it should add selected tag to the input fields and close the popover.
So here I even have to implement 2 way of communication between 2 view controllers.
Questions:
I feel that I'm doing something wrong. The logic of this tags-popover looks too complicated for me. Maybe an easier solution exists, but I just don't know how to do this.
What the best way to display the list of available tags in NSTextField? Should I use a popover with another view controller?
How to communicate best in this case between 2 different view controllers? (I have to send InputField value to PopoverViewController, filter results there and display them. Then, I have to send back to MainViewController selected tag)
My code:
Here is some parts of my code if you want to see it in more details:
MainViewController:
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var InputField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
InputField.delegate = self
}
func controlTextDidChange(_ sender: Notification) {
if InputField.stringValue.contains("#") {
let controller = ToolTipVC.Controller()
let popover = NSPopover()
popover.contentViewController = controller
popover.behavior = .transient
popover.animates = true
popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxY)
}
}
ToolTipViewController:
import Cocoa
import EventKit
class ToolTipVC: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var ToolTipTable: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
ToolTipTable.delegate = self
ToolTipTable.dataSource = self
}
func numberOfRows(in tableView: NSTableView) -> Int {
return ReminderLists.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let cell = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView else { return nil }
cell.textField?.stringValue = "Row #\(row): " + ReminderLists[row].title
return cell
}
}
extension ToolTipVC {
static func Controller() -> ToolTipVC {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let identifier = NSStoryboard.SceneIdentifier("ToolTipId")
guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? ToolTipVC else {
fatalError("Why cant i find viewcontroller? - Check Main.storyboard")
}
return viewcontroller
}
}
I appreciate your feedback and advises.
Thank you in advance!
P.S.: I'm not a developer, I'm a Product Manager. This is my pet-project, so I know that my code is awful and has a lot of mistakes.
Please, don't judge me hard :)
At first glance it looks like when you call this line:
popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxY)
preferredEdge is set to NSRectEdge.maxY
Try changing this from Y to X
popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxX)
Is there a reason you are usin NSPopover?
If that doesnt work try this:
let controller = MyPopViewController()
controller.modalPresentationStyle = UIModalPresentationStyle.popover
let popController = controller.popoverPresentationController
popController?.permittedArrowDirections = .any
popController?.delegate = self
popController?.sourceRect = //set the frame here
popController?.sourceView = //set the source view here
self.present(controller, animated: true, completion: nil)
Here is another approach, this will centre the pop over over a button. Which is what you are trying to do i am assuming with no up arrow? Is that correct?
So you can hit the filters button on the right side whihc you have and this pop over will appear centred.
#IBAction func buttonTap(sender: UIButton) {
// get a reference to the view controller for the popover
let popController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "popoverId")
// set the presentation style
popController.modalPresentationStyle = UIModalPresentationStyle.popover
// set up the popover presentation controller
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = sender // button
popController.popoverPresentationController?.sourceRect = sender.bounds
// present the popover
self.present(popController, animated: true, completion: nil)
}

Swift 4 Delegate doesn't work from childViewController

I have to code simple program. Just one login screen waiting for some password and after setting proper one, change screen to "program itself" and its features.
I decided to do it by container View and its subviews. everything works fine but delegate which should be responsible for triggering this change doesn't respond. In any option I tried, nothing has changed... it still doesn't respond.
This is code of ViewController (main):
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var container: NSView!
var vc1 : ViewController1 = ViewController1()
var vc2 : ViewController2 = ViewController2()
override func viewDidLoad() {
super.viewDidLoad()
//initialise of delegate:
vc1.delegate = self
// set subviews:
vc1 = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil).instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "ViewController1")) as! ViewController1
vc2 = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil).instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "ViewController2")) as! ViewController2
self.addChildViewController(vc1)
self.addChildViewController(vc2)
// set first view:
vc1.view.frame = self.container.bounds
self.container.addSubview(vc1.view)
}
func changeViews()
{
for sView in self.container.subviews {
sView.removeFromSuperview()
}
// set second view:
vc2.view.frame = self.container.bounds
self.container.addSubview(vc2.view)
}
}
extension ViewController: ViewController1_Delegate {
func passwdEntered(_ correctPasswd: Bool) {
if correctPasswd == true{
changeViews()
}
}
}
and this is First (SubView) ViewController code, where I'm entering passwd:
import Cocoa
protocol ViewController1_Delegate: class {
func passwdEntered(_ correctPasswd: Bool)
}
class ViewController1: NSViewController{
#IBOutlet weak var passwordField: PasswordField!
weak var delegate: ViewController1_Delegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func passwordFIeldEdited(_ sender: PasswordField) {
delegate?.passwdEntered(true/*for testing purpose always true*/)
}
}
Finally Second (SubView) ViewController code:
import Cocoa
class ViewController2: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
What I'm doing wrong?
First you say this:
var vc1 : ViewController1 = ViewController1()
override func viewDidLoad() {
super.viewDidLoad()
vc1.delegate = self
Then you say this:
vc1 = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"),
bundle: nil).instantiateController(
withIdentifier: NSStoryboard.SceneIdentifier(
rawValue: "ViewController1")) as! ViewController1
So, you set yourself as delegate on a ViewController1 instance, but then, in the very next line, you throw away that ViewController1 and replace it with another!
Move the line v1.delegate = self down to after you set the real value of vc1.