I’m embarrassed to be asking this question, but even with the wealth of information available on SO and Internet searches, I’m unable to to accomplish my goal, which is to resize an NSScrollView contained within an NSView.
The details:
I have an NSViewController that is the window content of the main application window. The view controller contains an NSView to which I’ve programmatically added an NSScrollView, which in itself contains an NSTableView. The main application window and NSViewController are the freebies I get with IB, scroll view and table view are created programatically.
The NSTableView displays the rows and single column I’ve created as expected, but when I resize the window in the horizontal and vertical dimensions, the scroll view doesn’t resize. It appears that the containing view is restricted by the size I specify in creating the scroll view, but without a size the scroll view doesn’t call its delegate methods. My attempts to address that behavior don’t result in expected behavior and so clearly I don’t fully understand the cause of the problem.
My question then is this: what do I need to do to have the scroll view match the containing view when I resize the window?
//
// MyViewController.swift
// HelloTableViewXX
//
//
import Cocoa
fileprivate let ME = "ViewController"
class ViewController: NSViewController
{
private var dataArray: [String] = []
override func viewDidAppear()
{
super.viewDidAppear()
setupView()
setupTableView()
}
func setupView ()
{
self.view.translatesAutoresizingMaskIntoConstraints = false
}
func setupTableView ()
{
let tableView = NSTableView ()
tableView.headerView = nil
let column = NSTableColumn ()
column.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
column.width = 400
column.minWidth = 40
column.maxWidth = 4000
tableView.addTableColumn(column)
tableView.delegate = self
tableView.dataSource = self
let scrollView = NSScrollView (frame: self.view.bounds)
//scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = true
self.view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
scrollView.documentView = tableView
}
}
extension ViewController : NSTableViewDataSource
{
func numberOfRows(in tableView: NSTableView) -> Int
{
return 20
}
}
extension ViewController : NSTableViewDelegate
{
func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
{
let text = "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz"
var v = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier.init(rawValue: "TableColumn"), owner: self) as? NSTextField
if v == nil
{
v = NSTextField ()
v?.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
v?.maximumNumberOfLines = 1
}
else
{
print (ME + ".\(#function) tableView reuse")
}
v!.stringValue = text
v!.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
return v!
}
}
I have an answer for this specific issue. I created a separate project, with IB instantiated NSViewController, and an embedded NSScrollView and NSTableView, giving the expected view hierarchy of controller, view, scrollview, clip view, etc., and configured the settings in IB to produce the results I wanted. I then opened the storyboard in an XML editor, and used the definitions as a guide for the settings in the project with my programmatically set scroll view and table view, which solved my problem. I now have a resizing scroll view and table as the window is resized. The code looks like this:
import Cocoa
class MyTableViewController: NSViewController
{
private var initialized = false
private var dataArray: [String] = []
override func viewDidAppear()
{
super.viewDidAppear()
loadData()
//setupView()
setupTableView()
}
func setupView ()
{
}
func setupTableView ()
{
let tableView = NSTableView ()
tableView.headerView = nil
tableView.columnAutoresizingStyle = .lastColumnOnlyAutoresizingStyle
tableView.autoresizesSubviews = true
tableView.autoresizingMask = [.width, .height]
let column = NSTableColumn ()
column.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
column.width = 426
column.minWidth = 40
column.maxWidth = 1000
column.resizingMask = [.autoresizingMask, .userResizingMask] // verify in debugger
tableView.addTableColumn(column)
tableView.delegate = self
tableView.dataSource = self
let scrollView = NSScrollView (frame: self.view.bounds)
scrollView.autoresizesSubviews = true
scrollView.autoresizingMask = [.height, .width]
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = true
self.view.addSubview(scrollView)
self.view.addConstraint(NSLayoutConstraint (item: self.view, attribute: .trailing, relatedBy: .equal, toItem: scrollView, attribute: .trailing, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: self.view, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 20))
scrollView.documentView = tableView
}
}
extension MyTableViewController : NSTableViewDataSource
{
func numberOfRows(in tableView: NSTableView) -> Int
{
return dataArray.count
}
}
extension MyTableViewController : NSTableViewDelegate
{
func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
{
let text = dataArray [row]
var v = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier.init(rawValue: "TableColumn"), owner: self) as? NSTextField
if v == nil
{
v = NSTextField ()
v?.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
v?.maximumNumberOfLines = 1
v?.autoresizingMask = [.width]
v?.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal)
v?.translatesAutoresizingMaskIntoConstraints = false
}
else
{
print (ME + ".\(#function) tableView reuse")
}
v!.stringValue = text
v!.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
v!.frame = CGRect(x: 0, y: 0, width: 3000, height: 0)
return v!
}
Related
What does this warning mean? In my main app, whenever this warning is logged, the outline view begins to behave weirdly after that, overlapping rows and not responding to clicks anymore. When not using automatic row heights or group rows, the warning doesn't appear anymore.
An interesting thing is that even if there are no group rows, simply implementing the data source method
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
return false
}
makes the warning appear.
Here is the full code:
import Cocoa
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
var outlineView: NSOutlineView!
let data: Any = [[[[[[[0], [0, [0,0,0]]], [0, [0, [0,0]],0,0,0,0,0]]], [[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],1]]]]
override func loadView() {
outlineView = NSOutlineView(dataSource: self, delegate: self, columns: [
NSTableColumn(identifier: "", title: "", width: 400),
NSTableColumn(identifier: "", title: "", width: 100)
])
let scrollView = NSScrollView(documentView: outlineView)
view = NSView()
view.addSubview(scrollView)
NSLayoutConstraint.constrain(scrollView, toMarginsOf: view, edgeInsets: .zero)
outlineView.reloadData()
expandItem(data)
}
private func expandItem(_ item: Any) {
if let item = item as? [Any] {
outlineView.expandItem(item)
for content in item {
expandItem(content)
}
}
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return item is [Any]
}
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
return false
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
return ((item ?? data) as! [Any]).count
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
return ((item ?? data) as! [Any])[index]
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let view = NSTableCellView()
return view
}
}
extension NSOutlineView {
convenience init(dataSource: NSOutlineViewDataSource, delegate: NSOutlineViewDelegate, columns: [NSTableColumn]) {
self.init()
self.dataSource = dataSource
self.delegate = delegate
for column in columns {
addTableColumn(column)
}
usesAutomaticRowHeights = true
}
}
extension NSTableColumn {
convenience init(identifier: String, title: String, width: Double, minWidth: Double = 10, maxWidth: Double = .infinity) {
self.init(identifier: NSUserInterfaceItemIdentifier(identifier))
self.title = title
self.width = width
self.minWidth = minWidth
self.maxWidth = maxWidth
}
}
extension NSLayoutConstraint {
static func constrain(_ view: NSView, toMarginsOf superview: NSView, edgeInsets: NSEdgeInsets) {
NSLayoutConstraint.activate([NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: superview, attribute: .top, multiplier: 1, constant: edgeInsets.top), NSLayoutConstraint(item: view, attribute: .left, relatedBy: .equal, toItem: superview, attribute: .left, multiplier: 1, constant: edgeInsets.left), NSLayoutConstraint(item: superview, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: edgeInsets.bottom), NSLayoutConstraint(item: superview, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: edgeInsets.right)])
}
}
extension NSEdgeInsets {
static var zero: NSEdgeInsets {
return NSEdgeInsets(all: 0)
}
init(all: Double) {
self.init(top: all, left: all, bottom: all, right: all)
}
}
extension NSScrollView {
convenience init(documentView: NSView) {
self.init()
self.documentView = documentView
hasVerticalScroller = true
translatesAutoresizingMaskIntoConstraints = false
}
}
I have a programmatically created NSTableView with n rows and 1 column. The embedded text field is selectable, and not editable. I am able to observe the mouseDown event in the text field, but as you can see in the image, the selected row is repositioned and word wrapped. I suspect this may be related to the field editor, but my MacOS odyssey has proven me wrong innumerable times.
My question: What do I need to do to maintain the layout of the selected row to be consistent with the other rows in the table?
Update: adding a textfield.cell.wraps = false to the tableview delegate eliminated the word wrap, but still have the issue with the selected text field indented.
Code snippets:
From the table delegate:
func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
{
let text = dataArray [row]
var v = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier.init(rawValue: "TableColumn"), owner: self) as? MyTextFieldExt
if v == nil
{
v = MyTextFieldExt ()
v?.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
v?.maximumNumberOfLines = 1
v?.autoresizingMask = [.width]
v?.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal)
v?.translatesAutoresizingMaskIntoConstraints = false
v?.isEditable = false
v?.isSelectable = true
v?.cell?.wraps = false
}
v!.stringValue = text
v!.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
v!.frame = CGRect(x: 0, y: 0, width: 3000, height: 0)
return v!
}
From the text field (custom) that traps the mouseDown:
override func mouseDown(with event: NSEvent)
{
super.mouseDown(with: event)
let cEditor = self.currentEditor() as? NSTextView
let localPos = convert (event.locationInWindow, to: nil)
let location = cEditor?.selectedRange().location
if let r = cEditor?.selectedRange()
{
self.select(withFrame: self.frame, editor: cEditor!, delegate: self, start: r.location, length: r.length+10)
}
}
From the view controller that creates the scrollview, table view, and column:
func setupTableView ()
{
let tableView = MyTableViewExt ()
tableView.selectionHighlightStyle = .none
tableView.headerView = nil
tableView.columnAutoresizingStyle = .lastColumnOnlyAutoresizingStyle
tableView.autoresizesSubviews = true
tableView.autoresizingMask = [.width, .height]
let column = NSTableColumn ()
column.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
column.width = 426
column.minWidth = 40
column.maxWidth = 1000
column.resizingMask = [.autoresizingMask, .userResizingMask] // verify in debugger
tableView.addTableColumn(column)
tableView.delegate = self
tableView.dataSource = self
let scrollView = NSScrollView (frame: self.view.bounds)
scrollView.autoresizesSubviews = true
scrollView.autoresizingMask = [.height, .width]
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = true
self.view.addSubview(scrollView)
self.view.addConstraint(NSLayoutConstraint (item: self.view, attribute: .trailing, relatedBy: .equal, toItem: scrollView, attribute: .trailing, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: self.view, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 20))
scrollView.documentView = tableView
}
So my problem was resolved by modifying the code in the text field mouseDown logic. The corrected code looks like this, with a change in setting the selected range:
override func mouseDown(with event: NSEvent)
{
super.mouseDown(with: event)
let cEditor = self.currentEditor() as? NSTextView
let localPos = convert (event.locationInWindow, to: nil)
let insertionPoint = cEditor?.characterIndexForInsertion(at: localPos)
let location = cEditor?.selectedRange().location
if let r = cEditor?.selectedRange()
{
// self.select(withFrame: self.frame, editor: cEditor!, delegate: self, start: r.location, length: r.length+10) <-- Source of problem
cEditor?.setSelectedRange(NSMakeRange(r.location, r.length+10)) <-- Solution to problem
}
}
And just to be clear, the statement of not requiring the textfield.cell?.wraps = false I made in a comment is incorrect. That line is needed in my table view delegate to avoid the word wrap.
I'm in the process of constructing a UI test for my macOS application. This is using Xcode 8 on macOS Sierra. One thing that I noticed is as I record my UI test, I receive a strange error when clicking on my custom control NSButton subclass. The error message is: Recorder Service Error: Left Mouse Down: Failed to find matching element - Xcode error
Upon googling this error it seems that this is an issue related to accessibility or a bug in AppKit or some combination of the two. I did try the same approach again using an NSView custom control subclass (using a mouseDown: override to simulate a button click) the UI test was able to find and successfully record this event in the test. However, there seems to be a very specific problem when doing this with NSButton.
Other things I tried:
In the identity inspector I did set the Identifier property to a specific value, but this also seemed to have no beneficial effect upon recording my UI test.
I also tried overriding:
override func accessibilityPerformPress() -> Bool {
return true
}
But this also seems to have no noticeable effect as NSButton already conforms to NSAccessibilityButton.
I also tried throwing a breakpoint in the UITest and printing the entire XCUIApplication tree via po XCUIApplication() in the Xcode console and I did notice that my custom control does not seem to appear in the tree hierarchy.
Here is a snapshot as to what my storyboard looks like in my little sample application:
I've included both a system push button (which can be found and properly recorded) as well as my custom control NSButton below it.
Also here is a snapshot of the XCUIApplication tree in the console:
The code for my NSButton subclass looks like this:
#IBDesignable class CustomButton : NSButton {
//MARK: - Properties
#IBInspectable var cornerRadius : Float = 0
#IBInspectable var backgroundColor : NSColor = NSColor.clear
#IBInspectable var buttonText : String?
#IBInspectable var textColor : NSColor = NSColor.white
#IBInspectable var selectedTextColor : NSColor = NSColor.white
#IBInspectable var borderColor : NSColor = NSColor.clear
#IBInspectable var fontSize : Int = 13
#IBInspectable var borderWidth : Float = 1.5
#IBInspectable var shouldBoldText : Bool = false
#IBInspectable var shouldUnderlineText : Bool = false
fileprivate var isSetup = false
fileprivate var isSelected = false
internal var buttonTextField : NSTextField?
//MARK: - Inteface Builder Managemenet
override func prepareForInterfaceBuilder() {
configureView()
}
override func accessibilityPerformPress() -> Bool {
return true
}
//MARK: - Init
override func awakeFromNib() {
super.awakeFromNib()
configureView()
}
//MARK: - View Configuration and Styling
/// Configures the control UI elements and all styling options.
internal func configureView() {
if isSetup == false {
setAccessibilityElement(true)
isSetup = true
configureButtonTextField()
styleView()
}
}
/// Configures the button text field and applies auto layout.
fileprivate func configureButtonTextField() {
buttonTextField = NSTextField(frame: bounds)
buttonTextField!.alignment = NSTextAlignment.center
buttonTextField!.isBordered = false
buttonTextField!.isEditable = false
buttonTextField!.isSelectable = false
buttonTextField!.isBezeled = false
buttonTextField!.drawsBackground = false
buttonTextField!.translatesAutoresizingMaskIntoConstraints = false
addSubview(buttonTextField!)
//Apply the auto layout.
let horizontalXConstraint = NSLayoutConstraint(item: buttonTextField!, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let verticalYConstraint = NSLayoutConstraint(item: buttonTextField!, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0)
addConstraint(horizontalXConstraint)
addConstraint(verticalYConstraint)
}
/// Applies all the user defined runtime attributes. attriutes all have
/// fallback values in case none were set in interface builder.
fileprivate func styleView() {
wantsLayer = true
layer?.backgroundColor = backgroundColor.cgColor
layer?.cornerRadius = CGFloat(cornerRadius)
layer?.masksToBounds = true
if let buttonText = buttonText {
buttonTextField?.stringValue = buttonText
}
buttonTextField?.textColor = textColor
buttonTextField?.textColor = selectedTextColor
layer?.borderColor = borderColor.cgColor
layer?.borderWidth = CGFloat(borderWidth)
if shouldBoldText == true {
buttonTextField?.font = NSFont.boldSystemFont(ofSize: CGFloat(fontSize))
} else {
buttonTextField?.font = NSFont.systemFont(ofSize: CGFloat(fontSize))
}
}
/// Updates the text color for the button text field. Updatse the selected
/// text color to match the defined color.
///
/// - parameter textColor: The color to change the button textfield text to.
internal func styleButtonText(textColor : NSColor) {
self.textColor = textColor
buttonTextField?.textColor = textColor
selectedTextColor = textColor
}
//MARK: - Click Management
override func mouseDown(with event: NSEvent) {
isSelected = true
alphaValue = 0.7
buttonTextField?.textColor = selectedTextColor
}
override func mouseUp(with event: NSEvent) {
alphaValue = 1.0
buttonTextField?.textColor = textColor
isSelected = false
guard let target = target, let action = action else {
return
}
NSApp.sendAction(action, to: target, from: self)
}
//MARK: - Cursor Rect Management
override func resetCursorRects() {
addCursorRect(bounds, cursor: NSCursor.pointingHand())
}
}
I'm pretty stumped on this one. System controls seem to record and work just fine in UITesting but custom controls, at least of NSButton, seem to cause problems. If anyone has any ideas on what may be causing this it would be greatly appreciated.
what is the best way to display a bunch of NSViewControllers horizontal side by side inside a horizontal scrollable view?
Each "row" must be resizable by its own like a split view.
I tests a few ideas with split view and scroll view but can't get a good starting point.
Thanks for a kick.
ps.
UPDATE
here is what i've got:
I add a scrollview to my main view (ColoumnMasterViewController) border to border. I add a second ViewController to the storyboard and named them "coloumn_view_controller"
In the ColoumnMasterViewController i add the coloumn_view_controller.view a couple of times:
class ColoumnMasterViewController: NSViewController {
#IBOutlet weak var scrollView: NSScrollView!
override func viewDidLoad() {
for i in 1...10 {
let vc: NSViewController = self.storyboard?.instantiateControllerWithIdentifier("coloumn_view_controller") as! NSViewController
vc.view.setFrameOrigin(NSPoint(x: (130 * i), y: (i * 10)))
println("set to \(vc.view.bounds)")
scrollView.addSubview(vc.view)
}
scrollView.needsLayout = true
}
}
But scrolling is not available.
So, how can i create a view inside a scrollview that is bigger than the current viewport? I suppose if i solve this, I'm able to fix my other splitview problems.
Thanks a lot!
UPDATE 2
Finally i get it so far:
#IBOutlet weak var scrollingView: NSScrollView!
override func viewDidLoad() {
var contentView = NSView(frame: NSRect(x: 0, y: 0, width: 3000, height: self.view.frame.height))
contentView.wantsLayer = true
contentView.layer?.backgroundColor = NSColor.redColor().CGColor
for i in 0...10 {
let vc: NSViewController = self.storyboard?.instantiateControllerWithIdentifier("coloumn_view_controller") as! NSViewController
vc.view.setFrameOrigin(NSPoint(x: (i * 130), y: 0))
vc.view.setFrameSize(NSSize(width: 130, height: self.view.frame.height))
contentView.addSubview(vc.view)
}
scrollingView.documentView = contentView
autoLayout(contentView)
}
but my autoLayout() didn't work so well. How would you implement autoLayout that the contentView will pin on top, bottom, trailing and leading of the superview?
You could use a split view controller. You can have as many dividers as you want.
In recent versions of OSX view controllers can host the content of other view controllers.
Make a "Super View Controller"
Make the "Super View Controller" display the view property of each sub UIViewController
The "Super View Controller" should probably be a UITableViewViewController instance to help you scrolling and resizing rows
Question is quite broad, so it's unclear how much guidance do you need, don't hesitate to comment to request clarifications.
Finally i found a solution and stick it together into a demo projekt: https://github.com/petershaw/DynamicScrollViewExample
The Trick is to add a NSView and set it into the documentView, than manage the documentView correctly with autolayout:
override func viewDidLoad() {
contentView = NSView(frame: NSRect(x: 0, y: 0, width: 3000, height: self.view.frame.height))
scrollingView.documentView = contentView
autoLayout(contentView!)
}
Autolayout:
func autoLayout(contentView: NSView){
let topPinContraints = NSLayoutConstraint(
item: contentView
, attribute: NSLayoutAttribute.Top
, relatedBy: .Equal
, toItem: contentView.superview
, attribute: NSLayoutAttribute.Top
, multiplier: 1.0
, constant: 0
)
let bottomPinContraints = NSLayoutConstraint(
item: contentView
, attribute: NSLayoutAttribute.Bottom
, relatedBy: .Equal
, toItem: contentView.superview
, attribute: NSLayoutAttribute.Bottom
, multiplier: 1.0
, constant: 0
)
println("heightContraints \(contentView.superview!.frame.height)")
let heightContraints = NSLayoutConstraint(
item: contentView
, attribute: NSLayoutAttribute.Height
, relatedBy: .Equal
, toItem: contentView.superview!
, attribute: NSLayoutAttribute.Height
, multiplier: 1.0
, constant: 0
)
println("widthtContraints \(contentView.superview!.frame.width)")
let calculatedWith: CGFloat = (CGFloat) (contentView.subviews.count * 130)
widthtContraints = NSLayoutConstraint(
item: contentView
, attribute: NSLayoutAttribute.Width
, relatedBy: .Equal
, toItem: nil
, attribute: NSLayoutAttribute.Width
, multiplier: 1.0
, constant: 0
)
widthtContraints!.constant = calculatedWith
NSLayoutConstraint.activateConstraints([
topPinContraints
, bottomPinContraints
, heightContraints
, widthtContraints!
])
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.needsLayout = true
}
and than do a layout update after resizing the content:
func updateWidth(){
let calculatedWith: CGFloat = (CGFloat) (contentView!.subviews.count * 130)
if (widthtContraints != nil) {
widthtContraints!.constant = calculatedWith
}
}
But I don't know if this is a good solution? Comments are welcome. Now, I can replace the view with an NSSplitView subclass.
I am trying to build a custom keyboard using Swift for iOS 8 and I created each button programatically. When switching to my custom keyboard from build-in keyboard, the first time it's pretty slow as it took like 2 seconds to appear. I am not entirely sure if I am doing it correctly. Below are my code and I just show 2 buttons but there are a lot more:
class KeyboardViewController: UIInputViewController {
#IBOutlet var nextKeyboardButton: UIButton!
#IBOutlet var qButton: UIButton!
var buttonFontSize:CGFloat = 22.0
var gapBtwButton:CGFloat = +7.0
// a lot more button below
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
setupEnKeyboard()
}
func setupEnKeyboard(){
println("setupEnKeyboard")
addEnKeyboardButtons()
}
func addEnKeyboardButtons() {
addQButton()
addNextKeyboardButton()
// a lot more button to be added
}
func setupButton(label: String, functionName: Selector, imageOnButton: String) -> UIButton {
// initialize the button
let keyButton:UIButton = UIButton.buttonWithType(.System) as UIButton
var testVar: String? = imageOnButton
if imageOnButton.isEmpty {
keyButton.setTitle(label, forState: .Normal)
} else {
let image = UIImage(named: imageOnButton) as UIImage
keyButton.setImage(image, forState: .Normal)
}
keyButton.sizeToFit()
keyButton.setTranslatesAutoresizingMaskIntoConstraints(false)
// adding a callback
keyButton.addTarget(self, action: functionName, forControlEvents: .TouchUpInside)
// make the font bigger
keyButton.titleLabel?.font = UIFont.systemFontOfSize(self.buttonFontSize)
// add rounded corners
keyButton.backgroundColor = UIColor(white: 0.9, alpha: 1)
keyButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
keyButton.layer.cornerRadius = 5
return keyButton
}
func addQButton() {
qButton = setupButton("Q", functionName:"didTapQButton", imageOnButton:"")
view.addSubview(qButton)
var leftSideConstraint = NSLayoutConstraint(item: qButton, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1.0, constant: +6.0)
var topConstraint = NSLayoutConstraint(item: qButton, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: +10.0)
view.addConstraints([leftSideConstraint, topConstraint])
}
func didTapQButton(){
var proxy = textDocumentProxy as UITextDocumentProxy
proxy.insertText("q")
}
func addNextKeyboardButton() {
nextKeyboardButton = setupButton("N", functionName:"advanceToNextInputMode", imageOnButton:"globe")
view.addSubview(nextKeyboardButton)
var leftSideConstraint = NSLayoutConstraint(item: nextKeyboardButton, attribute: .Left, relatedBy: .Equal, toItem: showNumbersButton, attribute: .Right, multiplier: 1.0, constant: self.gapBtwButton)
var bottomConstraint = NSLayoutConstraint(item: nextKeyboardButton, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1.0, constant: -3.0)
view.addConstraints([leftSideConstraint, bottomConstraint])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated
}
override func textWillChange(textInput: UITextInput) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(textInput: UITextInput) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
var proxy = self.textDocumentProxy as UITextDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.Dark {
textColor = UIColor.whiteColor()
} else {
textColor = UIColor.blackColor()
}
//self.nextKeyboardButton.setTitleColor(textColor, forState: .Normal)
}
}
Appreciate any comment please :)
Thanks,
Mark Thien