Dynamically adding to NSStackView and Adding Scrolling Functionality - swift

Like the title says, I am trying to dynamically add NSTextViews and NSImageViews to a NSStackView. First, I added a Scroll View using IB and that made a hierarchy of Bordered Scroll View -> Clip View -> Main View -> Stack View (Vertical). I want to be able to dynamically add views to the stack view and be able to scroll through them all. I used autolayout on the Bordered Scroll View.
The problem: I tested this by adding 100 NSTextViews and incrementing their y-positions to stack up on each other. But, I cannot scroll to the top. I have been trying to understand this for a couple days but I just cannot figure it out. I cannot scroll and see all the text views, but when I increase the window size I can see more of them. TIA! `
import Cocoa
class ViewController: NSViewController {
var xLoc = CGFloat(0)
var yLoc = CGFloat(0)
#IBOutlet weak var mainView: NSStackView!
#IBOutlet weak var belowMainView: NSStackView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
for index in 1...100 {
var txt = "Num: \(index)"
setup(text: txt)
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func setup(text: String){
let tempTextView = NSTextView(frame: NSMakeRect(xLoc, yLoc, 320, 10))
tempTextView.append(text)
tempTextView.isEditable = false
tempTextView.translatesAutoresizingMaskIntoConstraints = false
// mainView.addSubview(tempTextView)
belowMainView.addSubview(tempTextView)
yLoc += 20
}
}
// Adding an append function to textView functionality
extension NSTextView {
func append(_ string: String) {
self.textStorage?.append(NSAttributedString(string: string))
// Scrolls to end of document if it is out of view
self.scrollToEndOfDocument(nil)
}
}`

Related

Move a view inside a view controller

I have a View controller and stack views set up as follows. Inside a Stack View, I have buttons.
I have a Player class inside the view controller and I have a function in it to move a card. I need my busViewButton to move on top of BtnP1A and I tried to achieve it as follows:
class GameViewController: UIViewController, Alertable {
#IBOutlet weak var btnP1A: UIButton!
#IBOutlet weak var btnP1B: UIButton!
var p1 = Player(n:"P1")
func start() -> Void{
p1.buttonA = btnP1A
p1.buttonB = btnP1B
p1.vc = self
}
.....
public class Player: Alertable{
var buttonA:UIButton!
var buttonB:UIButton!
weak var vc: GameViewController! = nil
.....
func move() -> Void{
var busFrame = self.vc.busViewButton.frame
//self.buttonA is BtnP1A
let frameA = self.buttonA.superview?.convert(self.buttonA.frame.origin, to: nil)
busFrame.origin.y = frameA!.y
busFrame.origin.x = frameA!.x
self.vc.busViewButton.frame = busFrame
}
.....
}
}
But this does not update the position correctly, the new position is different to the position of BtnP1A. Am I not getting the relative position correctly?
UPDATE:
It has something to do with being called from the nested class. Because, when a similar animation is called from the View Controller class, it works, but when moved inside the Player class, it does not work.
try this
p1.buttonA.isHidden = true
If you make the visibility of the contents false in stackviews, the other views will go to the top or elsewhere (of course depends on your constraints).

How to give an NSOutlineView a tiled background image that scrolls?

I have a standard NSOutlineView. I would like it to have a background image, which tiles vertically, and which scrolls together with the outline view cells.
I've somewhat achieved this using the following in my ViewController:
class ViewController: NSViewController {
#IBOutlet weak var outlineView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let image = NSImage(named: "tile") {
let color = NSColor.init(patternImage: image)
outlineView.backgroundColor = color
}
}
}
That works, except when you scroll past the top or bottom of the view (with the stretch provided by the containing scroll view).
I've tried putting the background image on the scroll view, but then it is static and doesn't scroll with the outline view's content.
I've also tried subclassing various objects in the view hierarchy and overriding their draw(_ dirtyRect: NSRect) method and doing:
self.wantsLayer = true
self.layer?.backgroundColor = ...etc
but got no success from that either.
Can anyone provide any suggestions?
I ended up creating a new custom NSView:
class MyView: NSView {
override func draw(_ dirtyRect: NSRect) {
if let image = NSImage(named: "Tile") {
let color = NSColor.init(patternImage: image)
color.setFill()
dirtyRect.fill()
}
super.draw(dirtyRect)
}
}
Then in my ViewController class I added an instance of the custom view, and used autolayout constraints to pin the new view to my outlineView's clip view starting 2000points above it, and ending 2000 below. This means no matter how far you over-scroll into the stretch area, you still see the tiled background.
class MyViewController: NSViewController {
#IBOutlet weak var outlineView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
guard let clipView = self.outlineView.superview else { return }
let newView = MyView(frame: .zero) // Frame is set by autolayout below.
newView.translatesAutoresizingMaskIntoConstraints = false
clipView.addSubview(newView, positioned: .below, relativeTo: self.outlineView)
// Add autolayout constraints to pin the new view to the clipView.
// See https://apple.co/3c6EMcH
newView.leadingAnchor.constraint(equalTo: clipView.leadingAnchor).isActive = true
newView.widthAnchor.constraint(equalTo: clipView.widthAnchor).isActive = true
newView.topAnchor.constraint(equalTo: clipView.topAnchor, constant: -2000).isActive = true
newView.bottomAnchor.constraint(equalTo: clipView.bottomAnchor, constant: 2000).isActive = true
}
}
I've removed other code from the above so hopefully I've left everything needed to illustrate the solution.

How to add a custom view from a xib to a UIScrollView? [duplicate]

This question already has answers here:
Load view from XIB as a subview of a scrollview
(2 answers)
Closed 3 years ago.
Problem
I am trying to add a custom view from a xib into a UIScrollView.
I added a UIScrollView to an empty ViewController in my storyboard. I also added 4 constraints for leading, trailing, top, bottom to Safe Area and set them all Equal to 0.
Now in the code I want to add a custom view "EquipmentInfoView" to the existed UIScrollView
class EquipmentInfoVC: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
equipmentInfoView = EquipmentInfoView.instantiate()
scrollView.addSubview(equipmentInfoView)
}
}
And it does not work as the ScrollView does not scroll. How should I do it instead?
Here is the custom view "EquipmentInfoView"
class EquipmentInfoView: UIView {
#IBOutlet var nameTextView: UITextView!
#IBOutlet var descriptionTextView: UITextView!
#IBOutlet var equipmentImageView: UIImageView!
static func instantiate() -> EquipmentInfoView {
let view: EquipmentInfoView = initFromNib()
return view
}
}
Extra Notes
I know this is not the normal way to create a custom view from xib. However, this way I dont have to set the File's Owner to "EquipmentInfoView", instead I set the View's Custom Class to "EquipmentInfoView".
(Setting File's Owner to "EquipmentInfoView" gives me EXC_BAD_ACCESS when I do unit testing)
For clarity, this is how I would add this view to a normal (non-scroll) view:
equipmentInfoView = EquipmentInfoView.instantiate()
mainView.addSubview(equipmentInfoView)
equipmentInfoView.frame = mainView.bounds
equipmentInfoView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
You are probably missing the contentSize of your scrollView
class EquipmentInfoVC: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
equipmentInfoView = EquipmentInfoView.instantiate()
scrollView.contentSize = equipmentInfoView.frame.size
scrollView.addSubview(equipmentInfoView)
}
}

(swift) update a number to label will make the view get back to original position

demo image
When I update a number to label will make the view get back to original position.
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet var aview: UIView!
var number = 0
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func plus(sender: UIButton) {
number++
label.text = "number:\(number)"
}
#IBAction func move(sender: AnyObject) {
aview.frame.origin.y -= 20
}
}
I couldn't find the answer on web, please help me to fix this problem.Thank you very much!
Because your xib or Storyboard you set use Autolayout:
So if you don't set constrain system will auto generate it. When you change frame by set frame it effect but when you access to it. It will auto back to old position.
if you don't want it happen. You set in viewDidLoad:
self.view.translatesAutoresizingMaskIntoConstraints = false
Issue is probably related to a constraint reloading when the label reloads.
Instead of setting aview.frame.origin.y -= 20 you should make an outlet to the constraint holding the y position of your aView and then update the constant of that constraint outlet instead.

How to programmatically scroll to next view controller in Scroll View Container

I created a scroll view container that houses three view controllers. It's meant to mimic snapchat's swipe layout. however, I can't seem to get a code to manually switch to the next view controller without actually swiping (which I'm not interested in)
I tried calling the container class and setting it's scroll offset but it crashes... tried creating a delegate protocol, but delegate is returning nil... I'm stumped.
Here is my code:
class AViewController: UIViewController, ABViewControllerDelegate {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// 1) Create the three views used in the swipe container view
var ATVc : ATViewController = ATViewController(nibName: "ATViewController", bundle: nil);
var ACVc : ACViewController = ACViewController(nibName: "ACViewController", bundle: nil);
var ABVc : ABViewController = ABViewController(nibName: "ABViewController", bundle: nil);
// 2) Add in each view to the container view hierarchy
// Add them in opposite order since the view hieracrhy is a stack
self.addChildViewController(ABVc);
self.scrollView!.addSubview(ABVc.view);
ABVc.didMoveToParentViewController(self);
self.addChildViewController(ACVc);
self.scrollView!.addSubview(ACVc.view);
ACVc.didMoveToParentViewController(self);
self.addChildViewController(ATVc);
self.scrollView!.addSubview(ATVc.view);
ATVc.didMoveToParentViewController(self)
// 3) Set up the frames of the view controllers to align
// with eachother inside the container view
var adminFrame :CGRect = ATVc.view.frame;
adminFrame.origin.y = adminFrame.height;
ACVc.view.frame = adminFrame;
var BFrame :CGRect = ACVc.view.frame;
BFrame.origin.y = 2*BFrame.height;
ABVc.view.frame = BFrame;
// 4) Finally set the size of the scroll view that contains the frames
var scrollWidth: CGFloat = self.view.frame.width
var scrollHeight: CGFloat = 3 * self.view.frame.size.height
self.scrollView!.contentSize = CGSizeMake(scrollWidth, scrollHeight)
self.scrollView!.setContentOffset(CGPointMake(0, self.view.frame.height), animated: false)
var changeMe : String = "okay"
}
func scrollUp() {
println("clicked!")
self.scrollView.contentOffset.y - self.view.frame.height
}
}
and this is the view controller I'm trying to get out off by pressing a button..
protocol ABViewControllerDelegate {
func scrollUp()
}
class ABViewController: UIViewController {
let delegate = ABViewControllerDelegate?()
#IBAction func button(sender: AnyObject) {
println("button clicked!")
delegate!.scrollUp()
}
}
I feel like I'm leading myself on and that it can't be done!