macOS: Cannot center NSTextField inside custom NSView based class - macos

I am creating a custom NSView based class with this code.
import Foundation
import AppKit
#IBDesignable
class MacBotaoMudaDeCor : NSView {
lazy var etiqueta: NSTextField = {
let etiquetax = NSTextField()
etiquetax.textColor = corLigada
let xConstraint = NSLayoutConstraint(item: etiquetax,
attribute: .centerX,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1,
constant: 0)
let yConstraint = NSLayoutConstraint(item: etiquetax,
attribute: .centerY,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1,
constant: 0)
self.addConstraint(xConstraint)
self.addConstraint(yConstraint)
etiquetax.integerValue = self.numeroBotao
etiquetax.sizeToFit()
self.addSubview(etiquetax)
return etiquetax
}()
// creates a CALayer() and sets it to the layer property of the view
var mainLayer: CALayer{
get{
if layer == nil{
layer = CALayer()
}
return layer!
}
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
mainLayer.cornerRadius = cornerRadius
mainLayer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
mainLayer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: NSColor? {
didSet {
mainLayer.borderColor = borderColor?.cgColor
}
}
#IBInspectable var corDesligada: NSColor = NSColor.white
#IBInspectable var corLigada: NSColor = NSColor.black
#IBInspectable var ligado: Bool = false {
didSet {
self.ajustar(corFundo: ligado ? self.corLigada : self.corDesligada)
}
}
#IBInspectable var numeroBotao: Int = 0 {
didSet {
etiqueta.integerValue = numeroBotao
}
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.ajustar(corFundo: corDesligada)
self.ajustar(corEtiqueta: corLigada)
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.ajustar(corFundo: corDesligada)
self.ajustar(corEtiqueta: corLigada)
}
override func awakeFromNib() {
self.ajustar(corFundo: corDesligada)
self.ajustar(corEtiqueta: corLigada)
}
func ajustar(corFundo:NSColor) {
mainLayer.backgroundColor = corFundo.cgColor
}
func ajustar(corEtiqueta:NSColor) {
etiqueta.textColor = corLigada
}
}
The idea is to have a NSTextField centered on the view that this class represents.
Xcode crashes trying to install the constraints with this message.
2019-06-26 06:08:42.341770+0100 Failed to set (contentViewController)
user defined inspected property on (NSWindow): Constraint improperly
relates anchors of incompatible types:

addConstraint(_:):
The constraint must involve only views that are within scope of the receiving view.
Add the view before adding the constraints.

Related

Resize NSScrollView within superview

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!
}

Autofill Username and Password WKWebview Swift 3 Xcode

I am trying to make an app in Swift 3 using the WKWebView class to go to a website and autofill in the Username and password. I have tried search for days and only find answers using Obj-C and old versions of swift.
I tried using the solution proposed here Autofill Username and Password UIWebView Swift
However this did not work for me in Swift 3. Here is my example code:
import Foundation
import UIKit
import WebKit
class spot: UIViewController, UIWebViewDelegate {
#IBOutlet weak var container: UIView!
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
webView = WKWebView()
container.addSubview(webView)
}
override func viewDidAppear(_ animated: Bool) {
let frame = CGRect(x:0, y:0, width:container.bounds.width, height:container.bounds.height)
webView.frame = frame
// Insert URL below to change launched page
let urlStr = "https://login.salesforce.com/"
let url = NSURL(string: urlStr)!
let request = NSURLRequest(url: url as URL)
webView.load(request as URLRequest)
}
func webViewDidFinishLoad(_ webView: UIWebView!) {
let savedUsername = UserDefaults.standard.string(forKey: "USERNAME")
let savedPassword = UserDefaults.standard.string(forKey: "PASSWORD")
if savedUsername == nil || savedPassword == nil {return}
if ( countElements(savedUsername!) != 0 && countElements(savedPassword!) != 0) {
let loadUsernameJS = "var inputFields = document.querySelectorAll(\"input[name='username']\"); \\ for (var i = inputFields.length >>> 0; i--;) { inputFields[i].value = \'\(savedUsername)\';}"
let loadPasswordJS = "var inputFields = document.querySelectorAll(\"input[name='password']\"); \\ for (var i = inputFields.length >>> 0; i--;) { inputFields[i].value = \'\(savedPassword)\';}"
self.webView.stringByEvaluatingJavaScriptFromString(loadUsernameJS)
self.webView.stringByEvaluatingJavaScriptFromString(loadPasswordJS)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
One thing is very important here, the id (getElementById), double check that you are using correct id, which is used in your web form.
func webViewDidFinishLoad(_ webView: UIWebView) {
if (email == "" || password == "") {return}
// fill data
let fillForm = String(format: "document.getElementById('email').value = '\(email)';document.getElementById('pass').value = '\(password)';")
webView.stringByEvaluatingJavaScript(from: fillForm)
}
I know its a bit late but here is my code for how I have made it work. That said the server was expecting the string as I constructed it, but I am hoping you have the info on how your particular server is expecting the URLRequest to be constructed.
#IBOutlet weak var loadingWebPage: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
let savedUsername = UserDefaults.standard.string(forKey: "USERNAME")
let savedPassword = UserDefaults.standard.string(forKey: "PASSWORD")
let urlStr = "https://login.salesforce.com/"
myWebView = WKWebView()
myWebView.uiDelegate = self
myWebView.navigationDelegate = self
setUrlString = "\(urlStr)?username=\(savedUsername)&password=\(savedPassword)"
}
override func viewWillAppear(_ animated: Bool) {
view.addSubview(myWebView)
myWebView.translatesAutoresizingMaskIntoConstraints = false
let width = NSLayoutConstraint(item: myWebView, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: myWebView, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: 1.0, constant: -70)
let top = NSLayoutConstraint(item: myWebView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 70)
view.addConstraints([width,height,top])
self.myWebView.addSubview(loadingWebPage)
let myURL = URL(string: setUrlString)
let myRequest = URLRequest(url: myURL!)
myWebView.load(myRequest)
}
Hope this helps.

Swift - GLKit View CIFilter Image

I am trying to use a GLIKit View in order to modify an Image. The class I have so far is working well all the CIFilters except for the CILineOverlay it renders a black view. If I use any other effect it works well.
Why is the CILineOverlay not showing?
class ImageView: GLKView {
let clampFilter = CIFilter(name: "CIAffineClamp")!
let blurFilter = CIFilter(name: "CILineOverlay")!
let ciContext:CIContext
override init(frame: CGRect) {
let glContext = EAGLContext(API: .OpenGLES2)
ciContext = CIContext(
EAGLContext: glContext,
options: [
kCIContextWorkingColorSpace: NSNull()
]
)
super.init(frame: frame, context: glContext)
enableSetNeedsDisplay = true
}
required init(coder aDecoder: NSCoder) {
let glContext = EAGLContext(API: .OpenGLES2)
ciContext = CIContext(
EAGLContext: glContext,
options: [
kCIContextWorkingColorSpace: NSNull()
]
)
super.init(coder: aDecoder)!
context = glContext
enableSetNeedsDisplay = true
}
#IBInspectable var inputImage: UIImage? {
didSet {
inputCIImage = inputImage.map { CIImage(image: $0)! }
}
}
#IBInspectable var blurRadius: Float = 0 {
didSet {
//blurFilter.setValue(blurRadius, forKey: "inputIntensity")
setNeedsDisplay()
}
}
var inputCIImage: CIImage? {
didSet { setNeedsDisplay() }
}
override func drawRect(rect: CGRect) {
if let inputCIImage = inputCIImage {
clampFilter.setValue(inputCIImage, forKey: kCIInputImageKey)
blurFilter.setValue(clampFilter.outputImage!, forKey: kCIInputImageKey)
let rect = CGRect(x: 0, y: 0, width: drawableWidth, height: drawableHeight)
ciContext.drawImage(blurFilter.outputImage!, inRect: rect, fromRect: inputCIImage.extent)
}
}
}
The Apple docs states "The portions of the image that are not outlined are transparent." - this means you are drawing black lines over a black background. You can simply composite the output from the filter over a white background to make the lines appear:
let background = CIImage(color: CIColor(color: UIColor.whiteColor()))
.imageByCroppingToRect(inputCIImage.extent)
let finalImage = filter.outputImage!
.imageByCompositingOverImage(background)

IBInspectable not rendering Layer of NSView

I want to edit border width and background color of an NSView and made those values IBInspectable:
#IBInspectable var borderWidth: CGFloat{
set{
layer?.borderWidth = newValue
}
get{
return layer!.borderWidth
}
}
#IBInspectable var backgroundColor: NSColor{
set{
layer?.backgroundColor = newValue.CGColor
}
get{
return NSColor(CGColor: layer!.backgroundColor)!
}
}
In the init method, I wrote:
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
wantsLayer = true
layer?.setNeedsDisplay()
}
When I run the app, changes are shown correctly, but the view doesn't live-render in the interface builder. I don't get any errors or warnings either.
i found the solution myself: it seems that the init Method does not get called when the view gets rendered in the Interface builder. As a solution i had to add a global variable which creates a CALayer() when needed:
#IBDesignable class CDPProgressIndicator: NSView {
// creates a CALayer() and sets it to the layer property of the view
var mainLayer: CALayer{
get{
if layer == nil{
layer = CALayer()
}
return layer!
}
}
//referencing to mainLayer ensures, that the CALayer is not nil
#IBInspectable var borderWidth: CGFloat{
set{
mainLayer.borderWidth = newValue
}
get{
return mainLayer.borderWidth
}
}
#IBInspectable var backgroundColor: NSColor{
set{
mainLayer.backgroundColor = newValue.CGColor
}
get{
return NSColor(CGColor: mainLayer.backgroundColor)!
}
}
and now it finally renders in the interface builder as well.
The setting of wantsLayer is sufficient to have the layer created when running the app, but it doesn’t work in IB. You have to set the layer explicitly. I’d suggest doing this in prepareForInterfaceBuilder, that way you are using the approved technique, wantsLayer, in your app, but we handle the IB exception, too. For example:
#IBDesignable
class CustomView: NSView {
#IBInspectable var borderWidth: CGFloat {
didSet { layer!.borderWidth = borderWidth }
}
#IBInspectable var borderColor: NSColor {
didSet { layer!.borderColor = borderColor.cgColor }
}
#IBInspectable var backgroundColor: NSColor {
didSet { layer!.backgroundColor = backgroundColor.cgColor }
}
// needed because IB doesn't don't honor `wantsLayer`
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer = CALayer()
configure()
}
override init(frame: NSRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
configure()
}
private func configure() {
wantsLayer = true
...
}
}
That handles this IB edge case (bug?), but doesn’t alter the standard flow when running the app.

iOS 8 Custom Keyboard appear pretty slow

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

Resources