Swift Activity Indicator and Label - xcode

I'm trying to do a simple task of creating a activity indicator with a label that says, "loading" or "saving" or whatever I program it to say when it is running. I can not seem to figure out how to get it to be directly under my activity indicator though, right now it is right along the side of it and I want to to be centered below it.
Here is my code:
public func show(viewController : UIViewController) {
Async.main {
self.spinner = UIActivityIndicatorView(frame: CGRectMake(0, 0, self.size, self.size))
self.activityLabel = UILabel(frame: CGRectMake(0,0,200,200))
if let spinner = self.spinner {
spinner.activityIndicatorViewStyle = self.style
let screenSize: CGRect = UIScreen.mainScreen().bounds
spinner.center = CGPoint (x: screenSize.width/2 , y: screenSize.height/2)
spinner.hidesWhenStopped = true
viewController.view.addSubview(spinner)
spinner.startAnimating()
self.activityLabel?.center = CGPoint (x: screenSize.width/2 , y: screenSize.height/1.9 )
self.activityLabel?.text = self.textmessage
viewController.view.addSubview(self.activityLabel!)
}
}
}
Thanks for any help!

I think the problem you are having with the label being misaligned is the fact that you didn't set the label's textAlignment to .center. You need to add:
self.activityLabel.textAlignment = .center
That should put the text inside the label directly under the spinner.

Related

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.

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

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)

NSWindow view capture to image

Update: Nov.6
Thanks to pointum I revised my question.
On 10.13, I'm trying to write a view snapshot function as general purpose NSView or window extension. Here's my take as a window delegate:
var snapshot : NSImage? {
get {
guard let window = self.window, let view = self.window!.contentView else { return nil }
var rect = view.bounds
rect = view.convert(rect, to: nil)
rect = window.convertToScreen(rect)
// Adjust for titlebar; kTitleUtility = 16, kTitleNormal = 22
let delta : CGFloat = CGFloat((window.styleMask.contains(.utilityWindow) ? kTitleUtility : kTitleNormal))
rect.origin.y += delta
rect.size.height += delta*2
Swift.print("rect: \(rect)")
let cgImage = CGWindowListCreateImage(rect, .optionIncludingWindow,
CGWindowID(window.windowNumber), .bestResolution)
let image = NSImage(cgImage: cgImage!, size: rect.size)
return image
}
}
to derive a 'flattened' snapshot of the window is what I'm after. Initially I'm using this image in a document icon drag.
It acts bizarrely. It seems to work initially - window in center, but subsequently the resulting image is different - smaller, especially when window is moved up or down in screen.
I think the rect capture is wrong ?
Adding to pointum's answer I came up with this:
var snapshot : NSImage? {
get {
guard let window = self.window, let view = self.window!.contentView else { return nil }
let inf = CGFloat(FP_INFINITE)
let null = CGRect(x: inf, y: inf, width: 0, height: 0)
let cgImage = CGWindowListCreateImage(null, .optionIncludingWindow,
CGWindowID(window.windowNumber), .bestResolution)
let image = NSImage(cgImage: cgImage!, size: view.bounds.size)
return image
}
}
As I only want / need a single window, specifying 'null' does the trick. Well all else fails, the docs, if you know where to look :o.
Use CGWindowListCreateImage:
let rect = /* view bounds converted to screen coordinates */
let image = CGWindowListCreateImage(rect, .optionIncludingWindow,
CGWindowID(window.windowNumber), .bestResolution)
To save the image use something like this:
let dest = CGImageDestinationCreateWithURL(url, "public.jpeg", 1, nil)
CGImageDestinationAddImage(destination, image, nil)
CGImageDestinationFinalize(destination)
Note that screen coordinates are flipped. From the docs:
The coordinates of the rectangle must be specified in screen coordinates, where the screen origin is in the upper-left corner of the main display and y-axis values increase downward

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"))

Rotate NSImageView at its Center to Make it Spin

Swift 4, macOS 10.13
I have read a variety of answers on SO and still can't get an NSImageView to spin at its center instead of one of its corners.
Right now, the image looks like this (video): http://d.pr/v/kwiuwS
Here is my code:
//`loader` is an NSImageView on my storyboard positioned with auto layout
loader.wantsLayer = true
let oldFrame = loader.layer?.frame
loader.layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
loader.layer?.position = CGPoint(x: 0.5, y: 0.5)
loader.layer?.frame = oldFrame!
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(-1 * .pi * 2.0)
rotateAnimation.duration = 2
rotateAnimation.repeatCount = .infinity
loader.layer?.add(rotateAnimation, forKey: nil)
Any ideas what I am still missing?
I just created a simple demo which contains the handy setAnchorPoint extension for all views.
The main reason you see your rotation from a corner is that your anchor point is somehow reset to 0,0.
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var imageView: NSImageView!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
// Create red NSImageView
imageView = NSImageView(frame: NSRect(x: 100, y: 100, width: 100, height: 100))
imageView.wantsLayer = true
imageView.layer?.backgroundColor = NSColor.red.cgColor
window.contentView?.addSubview(imageView)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationDidBecomeActive(_ notification: Notification) {
// Before animate, reset the anchor point
imageView.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
// Start animation
if imageView.layer?.animationKeys()?.count == 0 || imageView.layer?.animationKeys() == nil {
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = 0
rotate.toValue = CGFloat(-1 * .pi * 2.0)
rotate.duration = 2
rotate.repeatCount = Float.infinity
imageView.layer?.add(rotate, forKey: "rotation")
}
}
}
extension NSView {
func setAnchorPoint(anchorPoint:CGPoint) {
if let layer = self.layer {
var newPoint = NSPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
var oldPoint = NSPoint(x: self.bounds.size.width * layer.anchorPoint.x, y: self.bounds.size.height * layer.anchorPoint.y)
newPoint = newPoint.applying(layer.affineTransform())
oldPoint = oldPoint.applying(layer.affineTransform())
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.anchorPoint = anchorPoint
layer.position = position
}
}
}
As I wondered many times myself on this question, here is my own simple method to rotate any NSView. I post it also as a self reminder. It can be defined in a category if needed.
This is a simple rotation, not a continuous animation. Should be applied to an NSView instance with wantsLayer = YES.
- (void)rotateByNumber:(NSNumber*)angle {
self.layer.position = CGPointMake(NSMidX(self.frame), NSMidY(self.frame));
self.layer.anchorPoint = CGPointMake(.5, .5);
self.layer.affineTransform = CGAffineTransformMakeRotation(angle.floatValue);
}
This is the result of a layout pass resetting your view's layer to default properties. If you check your layer's anchorPoint for example, you'll find it's probably reset to 0, 0.
A simple solution is to continually set the desired layer properties in viewDidLayout() if you're in a view controller. Basically doing the frame, anchorPoint, and position dance that you do in your initial setup on every layout pass. If you subclassed NSImageView you could likely contain that logic within that view, which would be much better than putting that logic in a containing view controller.
There is likely a better solution with overriding the backing layer or rolling your own NSView subclass that uses updateLayer but I'd have to experiment there to give a definitive answer.

Resources