Swift iPad - How to present a formSheet with custom size based on a table view content? - swift

In my app i present a form sheet in this way for iPad devices:
myViewController = .formSheet
present(myViewController, animated: true)
This ViewController is very simple: there is just a table view displaying n elements.
My goal is make the formSheet size the same as the table view with its n elements.
I actually achieved that with this code:
class MyViewControlller: UIViewController {
#IBOutlet weak var myTable: UITableView!
private var observer: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
myTable.delegate = self
myTable.dataSource = self
observer = myTable(\.contentSize, options: [.new], changeHandler: { [weak self] (table, _) in
guard let self = self else {
return
}
self.preferredContentSize = CGSize(width: table.contentSize.width, height: table.contentSize.height + 30.0)
self.view.layoutIfNeeded()
})
}
}
But the problem is that while presenting the formSheet (while it is sliding from bottom to the center of the screen) I can clearly see an animation where the bottom of the sheet is adjusting its size until it reaches the center of the screen.
What I'm trying to achieve is this: When the sheet starts sliding from the bottom to the center of the screen it already has the right dimensions.
Am I doing something wrong with the code?

Related

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 properly size content on ScrollView for all devices?

I am using a scrollview along with Nibs. The goal is to layout 3 nibs with image inside them and scroll through them. I am also using storyboards to layout the scrollview.
The issue is venting looks good besides on the iPhone Plus sizes. The nibs are not centered right and bleeds on the screen? how do I fix this? Please check my code below.
#IBOutlet private weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
imageArray = [imageOne, imageTwo, imageThree]
scrollView.contentSize = CGSize(width: self.view.bounds.width * CGFloat(imageArray.count), height: scrollView.frame.size.height)
loadOnboardingDescriptions()
}
// MARK: Load onboarding dscription features
func loadOnboardingDescriptions() {
for (index, image) in imageArray.enumerated() {
if let onboardingDescriptionView = Bundle.main.loadNibNamed(Constants.NibName.onboardingDescriptionView.rawValue, owner: self, options: nil)?.first as? OnboardingDescriptionView {
onboardingDescriptionView.onboardingImageView.image = UIImage(named: image["image"]!)
onboardingDescriptionView.frame.size.width = self.view.bounds.size.width
onboardingDescriptionView.frame.origin.x = CGFloat(index) * self.view.bounds.size.width
print(onboardingDescriptionView.frame.origin.x)
self.scrollView.addSubview(onboardingDescriptionView)
}
}
}
EDIT:
As suggested by Matt bellow, I should have called this function inside:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
loadOnboardingDescriptions()
}

Dynamically adding to NSStackView and Adding Scrolling Functionality

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)
}
}`

Displaying a UIAlertView adjacent to button pressed to call

I'm trying to figure out if theres a way to tie the location a programatically created UIAlertView is displayed on screen to the location of the button pressed to activate it.
I would like to have my alert view display directly below my button.
Thanks to gutenmorgenuhu for setting me on the right direction I was able to create a UIPopover that locates its self to the button pressed.
I first created a new UIViewController with the content of my popover (a UIPickerView in this case), that view is given the storyboard name Popover.
Then in my original view I created 2 buttons, topButton and bottomButton and linked them to their respective actions and outlets.
My code follows:
import UIKit
class ViewController: UIViewController, UIPopoverControllerDelegate, UIPopoverPresentationControllerDelegate {
//declare variables
var popOverLocation: CGRect!
// Set outlets for all buttons
#IBOutlet weak var topButton: UIButton!
#IBOutlet weak var bottomButton: UIButton!
//Add actions to buttons, first trigger popOverLocation to take the frame properties of the active button, then call the popover function.
#IBAction func topButton(sender: AnyObject) {
popOverLocation = topButton.frame
popOver()
}
#IBAction func bottomButton(sender: AnyObject) {
popOverLocation = bottomButton.frame
popOver()
}
//function for creating the popover
func popOver() {
//Read the height, and XY coordinates of the active button
var h = popOverLocation.height
var x = popOverLocation.origin.x
var y = popOverLocation.origin.y + h
//create the popover
var popoverContent = self.storyboard!.instantiateViewControllerWithIdentifier("Popover") as UIViewController
var nav = UINavigationController(rootViewController: popoverContent)
nav.modalPresentationStyle = UIModalPresentationStyle.Popover
var popover = nav.popoverPresentationController! as UIPopoverPresentationController
popover.sourceView = self.view
popover.delegate = self
//set the size of the popover
popoverContent.preferredContentSize = CGSizeMake(200,300);
//set the direction the popover arrow is allowed to point
popover.permittedArrowDirections = UIPopoverArrowDirection.Up
//using the buttons coordinates set the location of the popover
popover.sourceRect = CGRectMake(x,y,0,0)
self.presentViewController(nav, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

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!