NSImageView (added programmatically) doesn't show image, but shows color - cocoa

Swift 4. Very simple project, all I did - just added a NSImageView programmatically, backgroundColor and NSImage from the .jpg file. I see the good pink color, but can't see the image at all! I tried many different approaches and some was successful (Image showed up well in collection view and if NSImageView was added manually in the story board) but I need in simple programmatically method. Here is all of my code:
class ViewController: NSViewController {
var image: NSImage = NSImage()
var ivTest = NSImageView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.ivTest)
self.ivTest.wantsLayer = true
self.ivTest.layer?.backgroundColor = NSColor.systemPink.cgColor
self.ivTest.layer?.frame = NSRect(x: 0, y: 0, width: 100, height: 100)
let manager = FileManager.default
var url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
url = url?.appendingPathComponent("night.jpg")
image = NSImage(byReferencing: url!)
if (image.isValid == true){
print("valid")
print("image size \(image.size.width):\(image.size.height)")
self.ivTest.image = image
} else {
print("not valid")
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
output:
result:
thank so much...
--- edited ---
Yes, thank You! Just added this and saw image:
self.ivTest.frame = NSRect(x: 0, y: 0, width: 100, height: 100)

Related

SwiftUI exporting the content of Canvas

Does anyone know how to export the content of a Canvas into an Image?
With SwiftUI, it is possible to generate an Image from a View with an extension
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
This works great for simple views like Button, but for Canvas it always generates an empty image.
For example, with the following code, the image generated by the button is fine, but the one of the Canvas is always empty.
import SwiftUI
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
struct ContentView: View {
var textView: some View {
Text("Hello, SwiftUI")
.padding()
.background(Color.green)
.foregroundColor(.white)
.clipShape(Capsule())
}
var canvas: some View {
Canvas { context, size in
var path = Path()
path.move(to: CGPoint(x: 0, y:0))
path.addLine(to: CGPoint(x: size.width/2, y:0))
path.addLine(to: CGPoint(x: size.width/2, y:size.height/2))
path.addLine(to: CGPoint(x: 0, y:size.height/2))
path.closeSubpath()
context.fill(path, with: .color(.blue))
}
}
var body: some View {
VStack {
textView
canvas
Button("Save to image: Canvas") {
if let view = canvas as? Canvas<EmptyView> {
let image = view.snapshot()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
Button("Save to image: Text") {
if let view = textView as? Text {
let image = view.snapshot()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
}
}
}
Apply a frame to the canvas and it should work. E.g.
canvas.frame(width: 300, height: 300)
Answer is here: Swift UI exporting content of canvas. Apply the frame in the line where you get the snapshot, i.e.
let newImage = canvasView.frame(width: 300, height: 300).snapshot()

Difficulty removing UITextView border while keeping indentation

I'm writing an iOS app where the user can add text fields, then drag them around the screen to reposition them, layout-style, sort of like Keynote.
I'm currently appending the user-added UITextFields to an #IBOutlet Collection and defaulting to .borderStyle = .roundedRect to get a faint border around the selected text, indicating the field is selected. Any UITextField will be set to .roundedRect border style when textFieldDidBeginEditing is called, and switch to textField.borderStyle = .none when textFieldDidEndEditing is called.
All seems to work with one problem: when switching border style to .none, the text field loses indentation that was around the border, shifting text outward and putting it in a spot where the user hadn't intended (graphic adds a background color red, just to show the shift, but I'll eventually allow the user to set background colors, so just shifting the UITextField isn't an option).
I've also tried adapting the answer at:
Create space at the beginning of a UITextField
setting a no-padding inset for the TextView when it's a .roundedRect, but adding padding when .borderStyle is .none. This seems to have no effect.
Other answers have suggested setting
textField.layer.borderColor = UIColor.clear.cgColor
or
textField.layer.borderWidth = 0.0
but these don't seem to have any effect, either
I'm eventually going to allow the user to change fonts & sizes of each TextField, so I'd like any indentation to be consistent whether the UITextField is selected or nots elected, and regardless of font choices.
Code is below. Recommendations are most welcome, as well as setting me on a new approach, if I'm missing a better solution.
Thanks!
John
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var screenView: UIView! // a 320 x 240 view
#IBOutlet var fieldCollection: [UITextField]! // Not connected, fields created programmatically
// below are used in .inset(by:) but seems to have no effect
let padding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
let noPadding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
override func viewDidLoad() {
super.viewDidLoad()
// hide keyboard if we tap outside of a field
let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:)))
tap.cancelsTouchesInView = false
self.view.addGestureRecognizer(tap)
createNewField()
}
// Select / deselect text fields
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.borderStyle = .roundedRect
// textField.bounds.inset(by: noPadding) // effect is the same if left out
}
func textFieldDidEndEditing(_ textField: UITextField) {
textField.borderStyle = .none
// textField.bounds.inset(by: padding) // effect is the same if left out
}
// UITextField created & added to fieldCollection
func createNewField() {
let newFieldRect = CGRect(x: 0, y: 0, width: 320, height: 30)
let newField = UITextField(frame: newFieldRect)
newField.borderStyle = .roundedRect
newField.isUserInteractionEnabled = true
newField.addGestureRecognizer(addGestureToField())
screenView.addSubview(newField)
if fieldCollection == nil {
fieldCollection = [newField]
} else {
fieldCollection.append(newField)
}
newField.delegate = self
newField.becomeFirstResponder()
}
func addGestureToField() -> UIPanGestureRecognizer {
var panGesture = UIPanGestureRecognizer()
panGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:)))
return panGesture
}
// event handler when a field(view) is dragged
#objc func draggedView(_ sender:UIPanGestureRecognizer){
sender.view!.becomeFirstResponder()
let selectedView = sender.view as! UITextField
selectedView.bringSubviewToFront(selectedView)
let translation = sender.translation(in: screenView)
selectedView.center = CGPoint(x: selectedView.center.x + translation.x, y: selectedView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: screenView)
}
#IBAction func addFieldPressed(_ sender: UIButton) {
createNewField()
}
}
I was able to work around the problem by subclassing UITextField:
class PaddedTextField: UITextField {
let padding = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
let noPadding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
override open func textRect(forBounds bounds: CGRect) -> CGRect {
if self.borderStyle == .none {
let content = bounds.inset(by: padding)
return content
} else {
return bounds.inset(by: noPadding)
}
}
}
I then changed the newField object creation from using UITextField to:
let newField = PaddedTextField(frame: newFieldRect)
One more change. The height needed to be more appropriately calculated. Since all of my text fields can start out the full length of the enclosing superview (320 points), I modified the original newFieldRect, used .sizeToFit() to create a textbox with the appropriate height. The other dimensions won't be correct b/c I don't have anything in the text view, but I extract the .height and reuse this with my original initliazation parameters.
newField.sizeToFit()
let newFieldHeight = newField.frame.height
newFieldRect = CGRect(x: 0, y: 0, width: 320, height: newFieldHeight)
newField.frame = newFieldRect
Here's hoping it helps save someone time.

macOS Swift CALayer.contents does not load image

The code below creates a red rectangle that is animated to move across the view from left to right. I would like to have an arbitrary shape loaded from an image to either superimpose or replace the rectangle. However, the circleLayer.contents = NSImage statement in the initializeCircleLayer function doesn't produce any effect. The diagnostic print statement seems to verify that the image exists and has been found, but no image appears in the view. How do I get an image into the layer to replace the animated red rectangle? Thanks!
CODE BELOW:
import Cocoa
class ViewController: NSViewController {
var circleLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
initializeCircleLayer()
simpleCAAnimationDemo()
}
func initializeCircleLayer(){
circleLayer.bounds = CGRect(x: 0, y: 0, width: 150, height: 150)
circleLayer.position = CGPoint(x: 50, y: 150)
circleLayer.backgroundColor = NSColor.red.cgColor
circleLayer.cornerRadius = 10.0
let testIm = NSImage(named: NSImage.Name(rawValue: "testImage"))
print("testIm = \(String(describing: testIm))")
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage
circleLayer.contentsGravity = kCAGravityCenter
self.view.layer?.addSublayer(circleLayer)
}
func simpleCAAnimationDemo(){
circleLayer.removeAllAnimations()
let animation = CABasicAnimation(keyPath: "position")
let startingPoint = NSValue(point: NSPoint(x: 50, y: 150))
let endingPoint = NSValue(point: NSPoint(x: 600, y: 150))
animation.fromValue = startingPoint
animation.toValue = endingPoint
animation.repeatCount = Float.greatestFiniteMagnitude
animation.duration = 10.0
circleLayer.add(animation, forKey: "linearMovement")
}
}
Why it doesn't work
The reason why
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage
doesn't work is because it's a reference to the cgImage(forProposedRect:context:hints:) method, meaning that its type is
((UnsafeMutablePointer<NSRect>?, NSGraphicsContext?, [NSImageRep.HintKey : Any]?) -> CGImage?)?
You can see this by assigning NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage to a local variable and ⌥-clicking it to see its type.
The compiler allows this assignment because circleLayer.contents is an Any? property, so literally anything can be assigned to it.
How to fix it
As of macOS 10.6, you can assign NSImage objects to a layers contents directly:
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))

Programmatically Set Up UISlider Not Reacting to Input

I am attempting to set up a UISlider programmatically but cannot get it to work. On the one hand slider.addTarget() works perfectly. When I move the slider the function rotateSlider(_:) gets called perfectly.
On the other hand when I try to set the start value programmatically it does not move the handle. Also if I try to hide the slider with slider.isHidden = true, nothing happens and it is not removed from the view.
var slider1: UISlider {
let frame = CGRect(x: 0, y: 0, width: 300, height: 40)
let slider1 = UISlider(frame: frame)
slider1.center = CGPoint(x: Int(self.view.frame.width) / 2, y: Int(self.view.frame.height) / 2)
slider1.minimumValue = -3
slider1.maximumValue = 3
slider1.addTarget(self, action: #selector(self.rotateSlider(_:)), for: .allTouchEvents)
return slider1
}
#objc func rotateSlider(_ sender: UISlider) {
print("rotated")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(slider1)
slider1.setValue(1.0, animated: true) // This does not work
slider1.isHidden = true // This does not work
}
I know it is something trivial that I am missing?!
Thanks.

Text Field with Only top and Bottom Border using swift 2

I have a page for forgot password. It has only a text field asking the user to fill in their email address. The Designer designed the text field with top and bottom border only.
I tried answer from here UITextField Only Top And Bottom Border
but in the result it only shows bottom border for the text field.
Like in the image i would like to create a grey border for top and bottom
To remove Fights with views you could create a tableView with a static cell that contains a TextField. Voila done... Top and bottom border comes for free and you will use standard apple stuff :)
If you really want to draw the layers than follow the steps on your linked questions:
CALayer *topBorder = [CALayer layer];
topBorder.frame = CGRectMake(0, 0, self.frame.size.width, 1);
topBorder.backgroundColor = [UIColor blackColor].CGColor;
[myTextField.layer addSublayer:topBorder];
CALayer *bottomBorder = [CALayer layer];
bottomBorder.frame = CGRectMake(0, self.frame.size.height - 1, self.frame.size.width, 1);
bottomBorder.backgroundColor = [UIColor blackColor].CGColor;
[myTextField.layer addSublayer:bottomBorder];
In Swift:
let topBorder = CALayer()
topBorder.frame = CGRectMake(0, 0, bounds.size.width, 1)
topBorder.backgroundColor = UIColor.grayColor()
textField.layer.addSublayer(topBorder)
let bottomBorder = CALayer()
bottomBorder.frame = CGRectMake(0, bounds.size.height-1, bounds.size.width, 1)
bottomBorder.backgroundColor = UIColor.grayColor()
textField.layer.addSublayer(bottomBorder)
Thanks #El Captain for the valuable comment and nice answer by #Bjorn Ro even if it was in Objective-c i think.
And my answer for the question is (i'm using swift 2 Xcode 7)
Override the function viewDidLayoutSubviews() in your swift file. And the Code for the same is
override func viewDidLayoutSubviews() {
// Creates the bottom border
let borderBottom = CALayer()
let borderWidth = CGFloat(2.0)
borderBottom.borderColor = UIColor.grayColor().CGColor
borderBottom.frame = CGRect(x: 0, y: forgotPasswordEmailText.frame.height - 1.0, width: forgotPasswordEmailText.frame.width , height: forgotPasswordEmailText.frame.height - 1.0)
borderBottom.borderWidth = borderWidth
forgotPasswordEmailText.layer.addSublayer(borderBottom)
forgotPasswordEmailText.layer.masksToBounds = true
// Creates the Top border
let borderTop = CALayer()
borderTop.borderColor = UIColor.grayColor().CGColor
borderTop.frame = CGRect(x: 0, y: 0, width: forgotPasswordEmailText.frame.width, height: 1)
borderTop.borderWidth = borderWidth
forgotPasswordEmailText.layer.addSublayer(borderTop)
forgotPasswordEmailText.layer.masksToBounds = true
}
forgotPasswordEmailText is the text field for entering Email
The Final output looks like this... with a gray Colour border (Screen shot of iPhone 4s Simulator)
Good suggestions for programatic solution posted so far. But I figured I'd share an Interfacebuilder solution....
1) Create view collection in your view controller
#IBOutlet private var borderViews: [UIView]?
2) Create 2 UIViews in interface builder 1px high constrained to where you want them around the textfield
3) Connect the 2 views in interface builder to borderViews IBOutlet
4) Customise appearance of both views by using setValue forKeyPath... for example, on success you may want the border to turn green
setValue(UIColor.green, forKeyPath: "borderViews.backgroundColor")
In Swift 3 use extension:
Create Swift file
import UIKit
extension UITextField {
func setBottomBorder() {
self.borderStyle = .none
self.layer.backgroundColor = UIColor.white.cgColor
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
self.layer.shadowOpacity = 1.0
self.layer.shadowRadius = 0.0
}
}
Call from anywhere:
PasswordField.setBottomBorder();
Here's a nice and easy Swift 4 implementation that handles resizing views as well :)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
viewToShadow.backgroundColor = .white
viewToShadow.layer.masksToBounds = false
viewToShadow.layer.sublayers?
.filter { layer -> Bool in
return layer.backgroundColor == UIColor.almostBlack.alpha(0.5).cgColor
}
.forEach { layer in
layer.removeFromSuperlayer()
}
[CGRect(x: 0.0, y: 0.0, width: viewToShadow.bounds.width, height: 0.5),
CGRect(x: 0.0, y: viewToShadow.bounds.height, width: viewToShadow.bounds.width, height: 0.5)]
.forEach { frame in
let layer = CALayer()
layer.frame = frame
layer.backgroundColor = UIColor.almostBlack.alpha(0.5).cgColor
viewToShadow.layer.addSublayer(layer)
}
}
Use handy extension for it
extension UITextField {
func addTopBorder(){
let bottomLine = CALayer()
bottomLine.frame = CGRect.init(x: 0, y: 0, width: self.frame.size.width, height: 1)
bottomLine.backgroundColor = UIColor.white.cgColor
self.borderStyle = UITextField.BorderStyle.none
self.layer.addSublayer(bottomLine)
}
func addBottomBorder(){
let bottomLine = CALayer()
bottomLine.frame = CGRect.init(x: 0, y: self.frame.size.height - 1, width: self.frame.size.width, height: 1)
bottomLine.backgroundColor = UIColor.white.cgColor
self.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "-", attributes: [NSAttributedString.Key.foregroundColor : #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)])
self.borderStyle = UITextField.BorderStyle.none
self.layer.addSublayer(bottomLine)
}
}
use it in you controller like this
yourTextfield.addTopBorder()
yourTextfield.addBottomBorder()
and don't forget to use it on main thread
DispatchQueue.main.async {
self.yourTextfield.addTopBorder()
self.yourTextfield.addBottomBorder()
}

Resources