NSScrollView containing NSStackView. Why the NSStackView items from bottom to Top? - swift

The NSScrollView containing a NSStackView. The NSStackView's items will add in runtime.The new NSStackView's items are from bottom to top, but I want they are from top to bottom.
The main.storyboard
The ViewController
import Cocoa
class ViewController: NSViewController {
var todoList:[Todo] = []
#IBOutlet weak var todosStackView: NSStackView!
#IBOutlet weak var todosScrollView: NSScrollView!
#IBAction func onEnter(sender: NSTextField) {
let todo = Todo()
todo.content = sender.stringValue
self.todoList.append(todo)
let todoItemView = TodoItem()
todoItemView.setButtonType(.SwitchButton)
todoItemView.todo=todo
todosStackView.addView(todoItemView, inGravity: .Top)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
The checkbox button class
import Cocoa
class TodoItem: NSButton {
var todo:Todo!{
didSet {
self.title = self.todo.content
self.state = self.todo.done ? NSOnState : NSOffState
}
}
func completeTodo(){
if(self.state == NSOnState){
self.todo.done = true
}else{
self.todo.done = false
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.setButtonType(.SwitchButton)
self.target = self
self.action = #selector(self.completeTodo)
}
}

By default, NSClipViews (and the base NSView) are not flipped and so coordinates (and scroll view positioning) are relative to the bottom left.
You can subclass NSClipView and override isFlipped to return true. Then in IB, set the clip view of the scroll view to that custom class.

You have your view (the parent of stack view) anchored to parent clip view via leading and bottom anchor. Try using leading and top instead. Note: your stack view itself seems to be aligned properly.

Related

How to separate view from viewcontroller using storyboard swift?

Practicing MVC pattern and want to separate view from viewcontroller using storyboard.
in main.storyboard I have a viewcontroller, and there are some uilabels in rootview.
to separate view code from viewcontroller, I selected view from viewcontroller scene , created FruitDetailView class and subclassed it in storyboard identity inspector.
And connected UILabels to FruitDetailView class.
class FruitDetailView: UIView {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var type: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
name.text = ""
type.text = ""
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(_ detail: Fruit) {
name.text = detail.name
type.text = detail.type
}
}
In the viewController , created FruitDetailView() instance.
And in loadview() methods, assigned fruitDetailView instance to view property.
class FruitDetailViewController: UIViewController {
private let fruitDetailView = FruitDetailViewController()
var fruit = Fruit()
override func loadView() {
view = fruitDetailView
fruitDetailView.update(fruit)
}
}
But when I run the app, app crashes with error.
How can I fix this?
Your problem is you want to reach your FruitDetail class created before even its properties being created. Put a breakpoint to the name.text = "" and run and you will see what i mean. To solve this just control your nil value safely :
if let label = name { // by the way consider in future to assign values like nameLabel rather than name
name.text = "put your string value"
}

Is it possible to modify the properties of a subclass from a parent class in Swift 4?

Via a method or closure, perhaps?
I created a subclass view controller of my superclass/parent view controller and placed labels with placeholder text in that subclass view controller.
I want to set the labels' values to blank strings from the superclass/parent view controller, or, specifically, from an IBAction function that causes the subclass view controller to appear.
Here is the code, first from the parent class, then from the subclass...
'''
class ViewController: UIViewController {
#IBAction func leavingView(){
self.EntryViewController.entryDateLabel.text = ""
self.EntryViewController.entryLabel.text = ""
self.dismiss(animated: true, completion: nil)
}
'''
then from the subclass...
'''
class EntryViewController: ViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
}
'''
I have come up with 2 solutions to this problem, without having the parent view controller know about its subclass.
In the first example the parent sets properties on itself that the child listens to (via the didSet method, it then updates its view accordingly. However, this isn't ideal because the entryDate and entry string fields are useless on their own, almost redundant in the parent.
class ParentViewController: UIViewController {
var entryDate: String?
var entry: String?
#IBAction func leavingView(){
self.entryDate = ""
self.entry = ""
self.dismiss(animated: true, completion: nil)
}
}
class ChildViewController: ParentViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
override var entryDate: String? {
didSet {
guard isViewLoaded else {
return
}
entryDateLabel.text = entryDate
}
}
override var entry: String? {
didSet {
guard isViewLoaded else {
return
}
entryLabel.text = entry
}
}
}
In my opinion, the second solution is clearer and keeps implementation details more separate because you're using instructions or events to notify the child view controllers.
class ParentViewController: UIViewController {
#IBAction func leavingView(){
self.dismiss(animated: true, completion: didLeaveView)
}
func didLeaveView() { }
}
class ChildViewController: ParentViewController {
#IBOutlet var entryDateLabel: UILabel!
#IBOutlet var entryLabel: UILabel!
override func didLeaveView() {
entryDateLabel.text = ""
entryLabel.text = ""
}
}
Since your requirement is not that much clear I have created a demo for you and into that demo I have added child ContainerViewController into parent ViewController and from that parent view controller you can change UILabel text when you click on UIButton of parent ViewController and code will be for ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnFromParentViewTapped(_ sender: Any) {
//Here get the child of your parent view controller
if let containerView = self.children[0] as? ContainerViewController {
containerView.lblContainer.text = ""
}
}
}
and ContainerViewController code will be:
class ContainerViewController: UIViewController {
#IBOutlet weak var lblContainer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Don't need to add much here because you are accessing it from parent view.
And your result will be:
As you can see when I click on button which title says Change Container label text the label text from ContainerViewController set to empty string.
For more info check THIS demo project.

How does the NSTextView make the text in the middle of the line

class ViewController: NSViewController {
#IBOutlet var editView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let
paragraphStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.lineHeightMultiple = 1.6
editView.defaultParagraphStyle = paragraphStyle;
//editView.typingAttributes[NSFontAttributeName] = paragraphStyle;
editView.typingAttributes[NSParagraphStyleAttributeName] = paragraphStyle;
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
This is all my code, in main.storyboard, there's only one NSTextView control.
But my text is drawn, and it's all started at the bottom of the row, which is not what I want. I want to be able to keep the text in the vertical position of the row. I have not found a case in this area, please help me answer. thank you
You can use NSAttributedStringKey.baselineOffset (NSBaselineOffsetAttributeName in Swift 3) to adjust the vertical position of the text within the line. You will of course need to write a little code to calculate your line height and find the correct baseline to use, but this code sample should give you the basic idea:
class ViewController: NSViewController {
#IBOutlet var editView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.lineHeightMultiple = 1.6
editView.defaultParagraphStyle = paragraphStyle
editView.typingAttributes[.paragraphStyle] = paragraphStyle
editView.typingAttributes[.baselineOffset] = 15
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}

Hide Custom View UIButton From UIViewController Class

Actually i have a Custom view with two button, and i want to hide it at runtime through UIViewController , So i don't get any exact thing to hide that button from UIViewcontroller class
Here is my CustomView class,
import UIKit
class BottomButtonUIView: UIView {
#IBOutlet weak var btnNewOrder: UIButton!
#IBOutlet weak var btnChat: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
// MARK: init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
if self.subviews.count == 0 {
setup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
if let view = Bundle.main.loadNibNamed("BottomButtonUIView", owner: self, options: nil)?.first as? BottomButtonUIView {
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
}
#IBAction func btnOrderNowClick(_ sender: Any) {
let VC1 = StoryBoardModel.orderDeatalStoryBord.instantiateViewController(withIdentifier: "NewOrderViewController") as! NewOrderViewController
VC1.isPush = false
let navController = UINavigationController(rootViewController: VC1) // Creating a navigation controller with VC1 at the root of the navigation stack.
let currentController = getCurrentVC.getCurrentViewController()
currentController?.present(navController, animated:true, completion: nil)
}
#IBAction func btnChatNowClick(_ sender: Any) {
}
func getCurrentViewController() -> UIViewController? {
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
}
I set it to UIView in StoryBoard, and then I create outlet of that view,
#IBOutlet weak var viewBottmNewOrder: BottomButtonUIView!
Now i want to hide btnNewOrder from UIViewcontroller class but when i use
viewBottmNewOrder.btnNewOrder.isHidden = true it cause null exception, Please do need full answer.
Please don't do like that. The required init(coder aDecoder: NSCoder) will call a lot of times when the BottomButtonUIView created from xib. And your custom view will look like:
[BottomButtonUIView [ BottomButtonUIView [btnNewOrder, btnChat]]].
So when you access to btnNewOrder like that:
viewBottmNewOrder.btnNewOrder it will null.
I think you should add your custom view in viewDidLoad of your `UIViewController'.

Start animation once a UIView appeared on screen

I have a custom UIView that I want to cover the screen once the user taps a button. It kind of simulates a custom view. There is child UIView in the custom UIView that should animate from the bottom (hidden at first) up to it's normal position (visible at the bottom). The problem that I am having is that it seems like layoutSubviews is a bad place to start doing animations. Where would the correct place be? Something like viewDidAppear but for UIViews.
In UIViewController:
let rect: CGRect = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height)
let alertView = AlertView(frame: rect)
view.addSubview(alertView)
In the AlertView:
import UIKit
class AlertView: UIView {
let nibName = "AlertView"
let animationDuration = 0.5
var view: UIView!
#IBOutlet weak var notificationView: UIView!
#IBOutlet weak var notificationBottomConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
viewSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
viewSetup()
}
override func didAddSubview(subview: UIView) {
super.didAddSubview(subview)
}
func viewSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
// move the notification view offscreen
notificationBottomConstraint.constant = -notificationView.frame.size.height
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiateWithOwner(self, options: nil)[0] as! UIView
}
override func layoutSubviews() {
super.layoutSubviews()
print("layoutSubviews")
animate()
}
func animate() {
// move the notification up
self.notificationBottomConstraint.constant = 0
UIView.animateWithDuration(animationDuration) { () -> Void in
self.view.setNeedsDisplay()
}
}
}
I suggest you to call your animate() function from one of these methods:
willMoveToSuperview:
didMoveToSuperview
These methods of UIView as needed to track the movement of the current view in your view hierarchy.
Reference: UIView class
Your final constraint value is not in your animate closure. Try this:
func animate() {
// move the notification up
UIView.animateWithDuration(animationDuration) { () -> Void in
self.notificationBottomConstraint.constant = 0
self.view.setNeedsDisplay()
}
}