NSController calling function in NSView - swift

I am trying to make NSViewControler work with NSView. I can't get myView to call any function in myView(NSView).
myView?.myFunctionName does not work and for NSView I get a nil error when I try set backgoundColor (myview?.layer?.backgroundColor = .white)
class ViewController: NSViewController {
#IBOutlet weak public var myview: NSView!
override func viewDidLoad() {
super.viewDidLoad()
myview?.wantsLayer = true
myview?.layer?.backgroundColor = .white
myview?.needsDisplay = true
}
override var representedObject: Any? {
didSet {
//Update the view, if already loaded.
}
}
}
import Cocoa
class myView: NSView {
var backgroundColor: NSColor?
override func draw(_ dirtyRect: NSRect) {
print("draw")
if let bg = backgroundColor {
bg.setFill()
dirtyRect.fill()
} else {
super.draw(dirtyRect)
print("error:\(String(describing: backgroundColor))")
self.changeBackgroundColor(color: .blue)
}
}
func changeBackgroundColor(color: NSColor) {
// print("change background\( bounds.size)")
// backgroundColor = color
var path = NSBezierPath()
path = NSBezierPath(rect: NSRect(origin:CGPoint(x: 0, y: 0), size: bounds.size))
// print("\(String(describing: path))")
path.close()
color.setFill()
path.fill()
setNeedsDisplay(self.bounds)
}
}

Related

Can't add a UIView loaded from XIB as a subView of a ViewController view

I want to implement a popup notification into my app when data was being updated successfully or not. To do that I:
created a .xib file: Screenshot
created a class where I load that NIB:
import UIKit
class PopupView: UIView {
static let instance = PopupView()
#IBOutlet weak var backgroundView: UIView!
#IBOutlet weak var popupView: UIVisualEffectView!
#IBOutlet weak var symbol: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("PopupView", owner: self)
popupView.layer.cornerRadius = 20
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func showPopup(title: String, message: String, symbol: UIImage, on viewController: UIViewController) {
self.titleLabel.text = title
self.descriptionLabel.text = message
self.symbol.image = symbol
guard let targetView = viewController.view else { return }
backgroundView.frame = targetView.bounds
targetView.addSubview(backgroundView)
}
In the above class I created a showPopup method where defined a backgroundView frame to be equal to ViewController bounds.
When I call that method in desired ViewController I receive the behaviour where my popupView shows itself and then went off the screen straight away (black area in the GIF): GIF
Would you be so kind to help me understand and fix the reason why the popupView went off the screen and not just equal to a ViewController bounds.
The Code after Shawn Frank's answer
PopupView class:
import UIKit
class PopupView: UIView {
#IBOutlet weak var popupView: UIVisualEffectView!
#IBOutlet weak var symbol: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
private func configure() {
if let views = Bundle.main.loadNibNamed("PopupView", owner: self) {
guard let view = views.first as? UIView else { return }
view.frame = bounds
addSubview(view)
}
}
func showPopup(title: String, message: String, symbol: UIImage, on viewController: UIViewController) {
titleLabel.text = title
descriptionLabel.text = message
self.symbol.image = symbol
popupView.layer.cornerRadius = 20
popupView.clipsToBounds = true
viewController.view.addSubview(self)
UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveLinear) {
self.popupView.center.y += 40
} completion: { _ in
UIView.animate(withDuration: 0.3, delay: 3.0, options: .curveLinear) {
self.popupView.center.y -= 80
} completion: { _ in
self.popupView.removeFromSuperview()
}
}
}
}
Call in desired ViewController:
let width = CGFloat(10)
let midX = (self.view.frame.width - width) / 2.0
let frame = CGRect(x: midX, y: 0, width: width, height: 135)
let popup = PopupView(frame: frame)
popup.showPopup(title: "Test", message: "Tested super test", symbol: UIImage(named: "checkmark.circle.fill")!, on: self)
Constraints in xib: Screenshot
Current result: GIF
I do not use XIB and storyboard too much these days so I also had to refresh my memory and I used this tutorial
This is the custom PopupView class, I prefer not to use singleton for this but it is my personal preference
class PopupView: UIView {
private let xibName = "PopupView"
#IBOutlet weak var visualEffectBackground: UIVisualEffectView!
#IBOutlet weak var titleLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
private func configure() {
if let views = Bundle.main.loadNibNamed(xibName, owner: self),
let popupView = views.first as? UIView {
popupView.frame = bounds
addSubview(popupView)
}
}
func showPopup(title: String,
on viewController: UIViewController) {
guard let targetView = viewController.view else { return }
titleLabel.text = title
layer.cornerRadius = 20
clipsToBounds = true
targetView.addSubview(self)
}
}
XIB is set up like this:
Then in the view controller:
#IBAction func didTapPopUp(_ sender: Any) {
// Give your own width, height etc
let width = CGFloat(180)
let midX = (view.frame.width - width) / 2.0
let frame = CGRect(x: midX, y: 100, width: width, height: 80)
let popup = PopupView(frame: frame)
popup.showPopup(title: "Hello", on: self)
}
Gives me this result:
Update based on artexhibit (OPs) comments
The custom view can get its frames in 3 ways that I can think of:
Directly from XIB
By providing frame during programmatic initialization
From the frame set when creating the view in storyboard
To make the view work for all these scenarios, we should not do any frame adjustment inside the custom view and leave it to the parent / container / superview
So I made the following changes to work for all scenarios:
class PopupView: UIView {
private static let xibName = "PopupView"
#IBOutlet weak var visualEffectBackground: UIVisualEffectView!
#IBOutlet weak var titleLabel: UILabel!
init() {
super.init(frame: .zero)
configure()
}
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
private func initializeWithNib() {
var popupView = UIView()
if let views = Bundle.main.loadNibNamed(PopupView.xibName,
owner: self),
let view = views.first as? UIView {
popupView = view
}
frame = popupView.bounds
addSubview(popupView)
}
private func initializeWithFrame() {
if let views = Bundle.main.loadNibNamed(PopupView.xibName,
owner: self),
let popupView = views.first as? UIView {
popupView.frame = bounds
addSubview(popupView)
}
}
private func configure() {
if frame == .zero {
initializeWithNib()
}
else {
initializeWithFrame()
}
layer.cornerRadius = 20
clipsToBounds = true
}
func showPopup(title: String,
on viewController: UIViewController) {
guard let targetView = viewController.view else { return }
print(frame)
titleLabel.text = title
targetView.addSubview(self)
}
}
Now this has the flexibility to work with all 3 scenarios:
// In code
#IBAction func didTapPopUp(_ sender: Any) {
// Default initializer
let popup = PopupView()
var originX = (view.frame.width - popup.frame.width) / 2.0
popup.frame.origin = CGPoint(x: originX, y: 100)
popup.showPopup(title: "Hello", on: self)
// Frame initializer
let popupWidth: CGFloat = 200
let popupHeight: CGFloat = 100
originX = (view.frame.width - popupWidth) / 2.0
let originY = (view.frame.height - popupHeight) / 2.0
let popupFrame = CGRect(x: originX,
y: originY,
width: popupWidth,
height: popupHeight)
let popupTwo = PopupView(frame: popupFrame)
popupTwo.showPopup(title: "Frames", on: self)
}
From storyboard
This gives the following results

SCNNode is not showing up

I'm new in Swift and ARKit. For some reason the SCNNode node I'm trying to display is not showing up. I'm working with SwiftUI. I defined in the next code block the function addNode that should render the node.
import Foundation
import ARKit
import SwiftUI
// MARK: - ARViewIndicator
struct ARViewIndicator: UIViewControllerRepresentable {
typealias UIViewControllerType = ARView
func makeUIViewController(context: Context) -> ARView {
return ARView()
}
func updateUIViewController(_ uiViewController:
ARViewIndicator.UIViewControllerType, context:
UIViewControllerRepresentableContext<ARViewIndicator>) { }
}
class ARView: UIViewController, ARSCNViewDelegate {
var arView: ARSCNView {
return self.view as! ARSCNView
}
override func loadView() {
self.view = ARSCNView(frame: .zero)
}
override func viewDidLoad() {
super.viewDidLoad()
arView.delegate = self
arView.scene = SCNScene()
}
// MARK: - Functions for standard AR view handling
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
arView.debugOptions = [.showFeaturePoints,
.showWorldOrigin]
arView.session.run(configuration)
arView.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
arView.session.pause()
}
func addNode(){
let node = SCNNode()
node.geometry = SCNBox(width: 0.1,
height: 0.1,
length: 0.1,
chamferRadius: 0)
node.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
node.position = SCNVector3(0,0,0.3)
arView.scene.rootNode.addChildNode(node)
arView.delegate = self
print(123)
}
// MARK: - ARSCNViewDelegate
func sessionWasInterrupted(_ session: ARSession) {}
func sessionInterruptionEnded(_ session: ARSession) {}
func session(_ session: ARSession, didFailWithError error: Error)
{}
func session(_ session: ARSession, cameraDidChangeTrackingState
camera: ARCamera) {}
}
... and that function is invoked when clicking the button "HOME"
import SwiftUI
import ARKit
// MARK: - NavigationIndicator
struct NavigationIndicator: UIViewControllerRepresentable {
typealias UIViewControllerType = ARView
func makeUIViewController(context: Context) -> ARView {
return ARView()
}
func updateUIViewController(_ uiViewController:
NavigationIndicator.UIViewControllerType, context:
UIViewControllerRepresentableContext<NavigationIndicator>) { }
}
struct ContentView: View {
#State var page = "Home"
var body: some View {
VStack {
ZStack {
NavigationIndicator()
VStack {
Spacer()
HStack {
Button("Home") {
let ar = ARView();
ar.addNode()
}.padding()
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color.white).opacity(0.7))
Spacer()
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Do you know why it's not showing up ?
Thanks in advance !
Use this approach for SceneKitView:
import SwiftUI
import ARKit
struct SceneKitView: UIViewRepresentable {
let arView = ARSCNView(frame: .zero)
#Binding var pressed: Bool
#Binding var node: SCNNode
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, ARSCNViewDelegate {
var control: SceneKitView
init(_ control: SceneKitView) {
self.control = control
}
func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
if control.pressed {
self.control.node = self.addCube()
self.control.arView.scene.rootNode.addChildNode(control.node)
}
}
fileprivate func addCube() -> SCNNode {
control.node.geometry = SCNBox(width: 0.25,
height: 0.25,
length: 0.25,
chamferRadius: 0.01)
control.node.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
control.node.geometry?.firstMaterial?.lightingModel = .phong
control.node.position = SCNVector3(0, 0,-2)
return control.node
}
}
func makeUIView(context: Context) -> ARSCNView {
arView.scene = SCNScene()
arView.delegate = context.coordinator
arView.autoenablesDefaultLighting = true
arView.debugOptions = .showFeaturePoints
// arView.allowsCameraControl = true
let config = ARWorldTrackingConfiguration()
arView.session.run(config)
return arView
}
func updateUIView(_ uiView: ARSCNView,
context: Context) { }
}
Then use this code for ContentView.
struct ContentView: View {
#State var pressed: Bool = false
#State var node = SCNNode()
var body: some View {
ZStack {
SceneKitView(pressed: $pressed, node: $node)
VStack {
Spacer()
HStack {
Button("Blue Cube") {
pressed.toggle()
}.padding()
.foregroundColor(.red)
Spacer()
}
}
}
}
}
P.S.
However, a strange issue occurs with ARSCNView in Simulator – after pressing a button a SCNBox appears only after tapping a screen with .allowsCameraControl = true.

Custom UITextfield with discontinuous bottom borders

I am now working on a UITextfield. I hope to know how to add four discontinuous bottom border to a UITextfield, and how to make the space between input digits larger to make them fit exactly on the four lines respectively. Moreover, if possible, how to make the line become black (while other lines remain grey) when users are inputing digit on that line? Thank you so much!
Use following subclass of UITextField and create textfield for each digit either in storyboard or programatically.
Note that each textfield has to set a tag, such as
1st Digit: textField1.tag=1
2nd Digit: textField1.tag=2
3rd Digit: textField1.tag=3
4th Digit: textField1.tag=4
class CustomTextField: UITextField {
private let normalStateColor = UIColor.lightGray.cgColor
private let focusStateColor = UIColor.black.cgColor
private let border = CALayer()
private let borderHeight: CGFloat = 4.0
// MARK:- Init
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
setup()
}
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
// MARK:- Overrides
override func layoutSubviews() {
super.layoutSubviews()
let size = self.frame.size
self.border.frame = CGRect(x: 0, y: size.height - borderHeight, width: size.width, height: borderHeight)
}
override func willMove(toSuperview newSuperview: UIView!) {
guard newSuperview != nil else {
NotificationCenter.default.removeObserver(self)
return
}
NotificationCenter.default.addObserver(self, selector: #selector(beginEdit),
name: UITextField.textDidBeginEditingNotification, object: self)
NotificationCenter.default.addObserver(self, selector: #selector(endEdit),
name: UITextField.textDidEndEditingNotification, object: self)
}
#objc func beginEdit() {
border.backgroundColor = self.focusStateColor
}
#objc func endEdit() {
border.backgroundColor = self.normalStateColor
}
private func setup() {
border.backgroundColor = self.normalStateColor
textAlignment = .center
borderStyle = .none
layer.addSublayer(border)
delegate = self
}
}
extension CustomTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text!.count < 1 && string.count > 0 {
textField.text = string
textField.superview?.viewWithTag(textField.tag + 1)?.becomeFirstResponder()
return false
} else if textField.text!.count >= 1 && string.count == 0 {
textField.text = ""
textField.superview?.viewWithTag(textField.tag - 1)?.becomeFirstResponder()
return false
}
return true
}
}
That yields
check this..
ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var txtOne: UITextField!
#IBOutlet weak var txtTwo: UITextField!
#IBOutlet weak var txtThree: UITextField!
#IBOutlet weak var txtFour: UITextField!
#IBOutlet weak var vwFour: UIView!
#IBOutlet weak var vwThree: UIView!
#IBOutlet weak var vwTwo: UIView!
#IBOutlet weak var vwOne: UIView!
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == txtOne {
vwOne.backgroundColor = .black
vwTwo.backgroundColor = .lightGray
vwThree.backgroundColor = .lightGray
vwFour.backgroundColor = .lightGray
} else if textField == txtTwo {
vwTwo.backgroundColor = .black
vwOne.backgroundColor = .lightGray
vwThree.backgroundColor = .lightGray
vwFour.backgroundColor = .lightGray
} else if textField == txtThree {
vwThree.backgroundColor = .black
vwTwo.backgroundColor = .lightGray
vwOne.backgroundColor = .lightGray
vwFour.backgroundColor = .lightGray
} else {
vwFour.backgroundColor = .black
vwTwo.backgroundColor = .lightGray
vwThree.backgroundColor = .lightGray
vwOne.backgroundColor = .lightGray
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}

NSTextField Fades Out After Subclassing

I've subclassed an NSTextField with the following code:
import Cocoa
class CustomSearchField: NSTextField {
override func draw(_ dirtyRect: NSRect) {
self.wantsLayer = true
let textFieldLayer = CALayer()
self.layer = textFieldLayer
self.backgroundColor = NSColor.white
self.layer?.backgroundColor = CGColor.white
self.layer?.borderColor = CGColor.white
self.layer?.borderWidth = 0
super.cell?.draw(withFrame: dirtyRect, in: self)
}
}
class CustomSearchFieldCell: NSTextFieldCell {
override func drawingRect(forBounds rect: NSRect) -> NSRect {
let minimumHeight = self.cellSize(forBounds: rect).height
let newRect = NSRect(x: rect.origin.x + 25, y: (rect.origin.y + (rect.height - minimumHeight) / 2) - 4, width: rect.size.width - 50, height: minimumHeight)
return super.drawingRect(forBounds: newRect)
}
}
This is all working fine and it draws my NSTextField just as I wanted. The only problem is, as soon as I make some other part of the interface the first responder (clicking outside the NSTextField), the text inside the NSTextField (placeholder or filled in text) is fading out. As soon as I click on it again, it fades back in. I've been searching for quiet a while now, but can't really figure out why this is happening. I just want the text to be visible all the time, instead of fading in and out.
It has to do with the CALayer that I'm adding to accomplish my styling.
Whenever I run the same settings from viewDidLoad on the textfield, it works like a charm. For example:
class ViewController: NSViewController {
#IBOutlet weak var searchField: NSTextField!
override func viewDidLoad() {
initCustomSearchField()
}
private func initCustomSearchField() {
searchField.wantsLayer = true
let textFieldLayer = CALayer()
searchField.layer = textFieldLayer
searchField.backgroundColor = NSColor.white
searchField.layer?.backgroundColor = CGColor.white
searchField.layer?.borderColor = CGColor.white
searchField.layer?.borderWidth = 0
searchField.delegate = self
}
}
draw method should really be used to draw the view not setting the properties.And for your problem don't set to self.layer directly use sublayer instead .
My suggestion for your code:
class CustomTextField :NSTextField {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView(){
textColor = .green
}
}
class CustomTextFieldCell: NSTextFieldCell {
override init(textCell string: String) {
super.init(textCell: string)
setupView()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView(){
backgroundColor = .red
}
override func drawingRect(forBounds rect: NSRect) -> NSRect {
let newRect = NSRect(x: (rect.width - rect.width/2)/2, y: 0, width: rect.width/2, height: 20)
return super.drawingRect(forBounds:newRect)
}
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
super.draw(withFrame: cellFrame, in: controlView)
controlView.layer?.borderColor = NSColor.white.cgColor
controlView.layer?.borderWidth = 2
}
}

My viewcontroller does not conform to GMSFetcherAutocompleteDelegate protocol

I'm trying to build an app where I input text into a textField and get the auto predict that google provides with its Fetcher GooglePlaces API. I have copied the code and altered it for my app but I can not figure out what I am doing wrong. I am using Swift Xcode8 Beta 3.
Please see my code below.
import UIKit
import GooglePlaces
import GoogleMaps
class NewTripAddCitiesViewController: UIViewController {
var fetcher: GMSAutocompleteFetcher?
#IBOutlet var textView: UITextView!
#IBOutlet var placesTextField: UITextField!
#IBOutlet var addToTripLabel: UIButton!
#IBOutlet var addCitiesLabel: UILabel!
#IBAction func addPlaceToTable(_ sender: AnyObject) {
}
#IBAction func savePlaces(_ sender: AnyObject) {
}
override func viewDidLoad() {
super.viewDidLoad()
addCitiesLabel.text = "Please select which places you'd like to visit in \(tripCountry):"
addToTripLabel.setTitle("Add to \"\(tripName)\"", for: [])
self.view.backgroundColor = UIColor.white()
self.edgesForExtendedLayout = []
// Set bounds to inner-west Sydney Australia.
let neBoundsCorner = CLLocationCoordinate2D(latitude: -33.843366, longitude: 151.134002)
let swBoundsCorner = CLLocationCoordinate2D(latitude: -33.875725, longitude: 151.200349)
let bounds = GMSCoordinateBounds(coordinate: neBoundsCorner, coordinate: swBoundsCorner)
// Set up the autocomplete filter.
let filter = GMSAutocompleteFilter()
filter.type = .establishment
// Create the fetcher.
fetcher = GMSAutocompleteFetcher(bounds: bounds, filter: filter)
fetcher!.delegate = self
placesTextField = UITextField(frame: CGRect(x: 5.0, y: 0, width: self.view.bounds.size.width - 5.0, height: 44.0))
placesTextField?.autoresizingMask = .flexibleWidth
placesTextField?.addTarget(self, action: Selector(("textFieldDidChange:")), for: .editingChanged)
textView = UITextView(frame: CGRect(x: 0, y: 45.0, width: self.view.bounds.size.width, height: self.view.bounds.size.height - 45.0))
textView?.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
textView?.text = "No Results"
textView?.isEditable = false
self.view.addSubview(placesTextField!)
self.view.addSubview(textView!)
}
func textFieldDidChange(placesTextField: UITextField) {
fetcher?.sourceTextHasChanged(placesTextField.text!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NewTripAddCitiesViewController: GMSAutocompleteFetcherDelegate {
func didAutocompleteWithPredictions(predictions: [GMSAutocompletePrediction]) {
let resultsStr = NSMutableAttributedString()
for prediction in predictions {
resultsStr.append(prediction.attributedPrimaryText)
resultsStr.append(AttributedString(string: "\n"))
}
textView?.attributedText = resultsStr
}
func didFailAutocompleteWithError(_ error: NSError) {
textView?.text = error.localizedDescription
}
}
func didFailAutocompleteWithError(_ error: NSError) {
has changes to
public func didFailAutocompleteWithError(_ error: Error) {