In the main.storyboard the rounded button aren't load - swift

I have add the class RoundButton in Swift with the following code:
//
// RoundButton.swift
//
import UIKit
#IBDesignable
class RoundButton: UIButton {
#IBInspectable var roundButton : Bool = false {
didSet {
if roundButton == true {
layer.cornerRadius = frame.height / 2
}
}
}
override func prepareForInterfaceBuilder() {
if roundButton == true {
layer.cornerRadius = frame.height / 2
}
}
}
Then I have activate the design in the button properties. In the main.storyboard the buttons are square, but in the build the buttons are round. And it comes this error:
file:///Users/anonymoushuman/Documents/Apps/Taschenrechner/Taschenrechner/Base.lproj/Main.storyboard: error: IB Designables: Failed to render and update auto layout status for ViewController (BYZ-38-t0r): The bundle “Taschenrechner.app” couldn’t be loaded because its executable couldn’t be located.

Your code works normally.
I also have the same version [Xcode 12.5.1].
I searched for the errors you experienced, and I think it would be good to refer to the links.
https://developer.apple.com/forums/thread/665826
I don't know what caused the error, but let me suggest another way.
This code can also achieve the same results you want to achieve.
import UIKit
extension UIButton {
func makeCircle() {
self.layer.masksToBounds = true
self.layer.cornerRadius = self.frame.width / 2
}
}
result

This is the extension I wrote. If you want to make a circle, not an ellipse, you can write button.makeRounded(nil). The caution here is that when you make a circle, the vertical and horizontal heights must be equal and the values must be specified.
extension UIButton {
func makeRounded(cornerRadius : CGFloat?) {
if let cornerRadius_ = cornerRadius {
self.layer.cornerRadius = cornerRadius_
} else {
self.layer.cornerRadius = self.layer.frame.height / 2
}
self.layer.masksToBounds = true
}

Related

How to change the width of an NSView in a transparent window

(Swift, macOS, storyboard)
I have an NSView in a transparent window
I have this in the viewDidLoad. To make the window transparent and the NSView blue:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
self.view.window?.isOpaque = false
self.view.window?.backgroundColor = NSColor.clear
}
view1.wantsLayer = true
view1.layer?.backgroundColor = NSColor.green.cgColor
I want to change the width with code when I click a button.
If it has constraints:
#IBAction func button1(_ sender: NSButton) {
view1Width.constant = 74
}
I tried without constraints and different ways to change the width. They all give the same results:
view1.frame = NSRect(x:50, y:120, width:74, height:100)
But there is still a border and a shadow where the old shape was. Why does it happen and how to solve it?
It only happens in specific circumstances:
If the window is transparent (and macOS)
I change the width and do not change the position y
The window must be active. If it is not (If I click to anywhere else) it looks as it should: the shadow around the changed NSView green.
(I have simplified the case to try to find a solution. I have created a new document and there is only this code and I am sure there is no other element)
Since the window is transparent you need to invalidate the shadows.
Apple states about invalidateShadow()
Invalidates the window shadow so that it is recomputed based on the current window shape.
Complete Self-Contained Test Program
It sets up the UI pogrammatically instead of using a storyboard. Other than that, the code is very close to your example.
Note the line:
view.window?.invalidateShadow()
in the onChange method.
import Cocoa
class ViewController: NSViewController {
private let view1 = NSView()
private let changeButton = NSButton()
private var view1Width: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
self.view.window?.isOpaque = false
self.view.window?.backgroundColor = NSColor.clear
}
view1.wantsLayer = true
view1.layer?.backgroundColor = NSColor.green.cgColor
}
#objc private func onChange() {
view1Width?.constant += 32
view.window?.invalidateShadow()
}
private func setupUI() {
changeButton.title = "change"
changeButton.bezelStyle = .rounded
changeButton.setButtonType(.momentaryPushIn)
changeButton.target = self
changeButton.action = #selector(onChange)
self.view.addSubview(view1)
self.view.addSubview(changeButton)
self.view1.translatesAutoresizingMaskIntoConstraints = false
self.changeButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
view1.centerYAnchor.constraint(equalTo: view.centerYAnchor),
view1.heightAnchor.constraint(equalToConstant: 128),
changeButton.topAnchor.constraint(equalTo: view1.bottomAnchor, constant:16),
changeButton.centerXAnchor.constraint(equalTo: view1.centerXAnchor)
])
view1Width = view1.widthAnchor.constraint(equalToConstant: 128)
view1Width?.isActive = true
}
}
Result
The desired result with an update of the shadows is accomplished:

Programmatically emptying UIStackView

I have a fairly simple code which, upon clicking a button, adds a randomly colored UIView to a UIStackView, and upon a different button click, removes a random UIView from the UIStackView.
Here's the code:
import UIKit
class ViewController: UIViewController, Storyboarded {
weak var coordinator: MainCoordinator?
#IBOutlet weak var stackView: UIStackView!
var tags: [Int] = []
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonPressed(_ sender: UIButton) {
switch sender.tag {
case 10:
let view = UIView(frame: CGRect(x: 0, y: 0, width: stackView.frame.width, height: 20))
var number = Int.random(in: 0...10000)
while tags.contains(number) {
number = Int.random(in: 0...10000)
}
tags.append(number)
view.tag = number
view.backgroundColor = .random()
stackView.addArrangedSubview(view)
case 20:
if tags.count == 0 {
print("Empty")
return
}
let index = Int.random(in: 0...tags.count - 1)
let tag = tags[index]
tags.remove(at: index)
if let view = stackView.arrangedSubviews.first(where: { $0.tag == tag }) {
stackView.removeArrangedSubview(view)
}
default:
break
}
}
}
extension CGFloat {
static func random() -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
}
extension UIColor {
static func random() -> UIColor {
return UIColor(
red: .random(),
green: .random(),
blue: .random(),
alpha: 1.0
)
}
}
I'm not using removeFromSuperview on purpose - since I would (later) want to reuse those removed UIViews, and that is why I'm using removeArrangedSubview.
The issue I'm facing is:
All UIViews are removed as expected (visually of course, I know they're still in the memory) until I reach the last one - which, even though was removed, still appears and filling the entire UIStackView.
What am I missing here?
You can understand removeArrangedSubview is for removing constraints that were assigned to the subview. Subviews are still in memory and also still inside the parent view.
To achieve your purpose, you can define an array as your view controller's property, to hold those subviews, then use removeFromSuperview.
Or use .isHidden property on any subview you need to keep it in memory rather than removing its contraints. You will see the stackview do magical things to all of its subviews.
let subview = UIView()
stackView.addArrangedSubview(subview)
func didTapButton(sender: UIButton) {
subview.isHidden.toggle()
}
Last, addArrangedSubview will do two things: add the view to superview if it's not in superview's hierachy and add contraints for it.

How to create block screen with circle loader

I am doing an app that does background job that can take some time
I want to show a loader in that time
I want a black screen with a simple loader in the front of it
and show it \ hide it,
when I do actions in the background
I want to do a simple half black square with loader circle
that also blocks presses to the screen
Like in this picture:
How can I achieve that and that ?
First create one UIView which you will put in front of your LogIn view. Then add UIActivityIndicatorView to the created UIView.
let loadingIndicatorView = UIView()
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
Now the loadingIndicatorView should have same frame size as your LogIN view. For color you can set your own color with alpha as you want to show LogIn content too. Initially keep it hidden and whenever you want to show it unhide it.
loadingIndicatorView.frame = view.frame
loadingIndicatorView.backgroundColor = .gray
loadingIndicatorView.isHidden = true
Now setup activityIndicatorView, it should be shown at centre,
activityIndicatorView.center = CGPoint(
x: UIScreen.main.bounds.size.width / 2,
y: UIScreen.main.bounds.size.height / 2
)
You can set some color to the indicator,
activityIndicatorView.color = .white
activityIndicatorView.hidesWhenStopped = true
Now add this activityIndicatorView to loadingIndicatorView and loadingIndicatorView to LogIn View.
loadingIndicatorView.addSubview(activityIndicatorView)
view.addSubview(loadingIndicatorView)
Lastly for showing do,
loadingIndicator.startAnimating()
loadingIndicatorView.isHidden = false
And for hiding,
loadingIndicator.stopAnimating()
loadingIndicatorView.isHidden = true
Updated Answer
Since the OP wanted an example code. Hence the updated answer. Hope everyone gets to learn something or the other out of it.
To start with, I created a subclass of UIView and named it PSOverlaySpinner and it looks something like below:
import UIKit
class PSOverlaySpinner: UIView {
//MARK: - Variables
private var isSpinning: Bool = false
private lazy var spinner : UIActivityIndicatorView = {
var spinner = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.white)
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.hidesWhenStopped = true
return spinner
}()
// MARK: - View Lifecycle Functions
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(frame: CGRect.zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.init(white: 0.0, alpha: 0.8)
self.isSpinning = false
self.isHidden = true
createSubviews()
}
deinit {
self.removeFromSuperview()
}
func createSubviews() -> Void {
self.addSubview(spinner)
setupAutoLayout()
}
// MARK: - Private Methods
private func setupAutoLayout() {
if #available(iOS 11.0, *) {
spinner.safeAreaLayoutGuide.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor).isActive = true
spinner.safeAreaLayoutGuide.centerYAnchor.constraint(equalTo: safeAreaLayoutGuide.centerYAnchor).isActive = true
} else {
// Fallback on earlier versions
spinner.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
spinner.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
// MARK: - Public Methods
public func show() -> Void {
DispatchQueue.main.async {
if !self.spinner.isAnimating {
self.spinner.startAnimating()
}
self.isHidden = false
}
isSpinning = true
}
public func hide() -> Void {
DispatchQueue.main.async {
if self.spinner.isAnimating {
self.spinner.stopAnimating()
}
self.isHidden = true
}
isSpinning = false
}
}
Now move onto the ViewController that you want to add this overlay view to. Since I create my views programmatically, I will show how to do it the same way, but you can easily do it via storyboard or xibs.
Step 1 : Initialize
public lazy var spinnerView : PSOverlaySpinner = {
let loadingView : PSOverlaySpinner = PSOverlaySpinner()
return loadingView
}()
Step 2 : Add as a subview
self.view.addSubview(spinnerView)
Step 3 : Set constraints
spinnerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
spinnerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
spinnerView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
spinnerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
Step 4 : To show PSOverlaySpinner
spinnerView.show()
Step 5 : To hide PSOverlaySpinner
spinnerView.hide()
That is it!!
If you want you can go ahead and modify the PSOverlaySpinner as per your needs. For example, you might want to add a UILabel below the spinner indicating him of the type of action taking place and so on.
Before
After
Old Answer
If you wish to do it manually then create a UIView with the its frame matching self.view.bounds, with 0.5-0.7 alpha and black background color. Add UIActivityIndicator as its subview constrained to its center. For a spinner specific to the image you will have to use the open sourced spinners made available. A couple of them can be found here. Once done add this view as the topmost subview in self.view.
You need to import this library SVProgressHUD and then set few properties like as follows:
SVProgressHUD.setDefaultStyle(SVProgressHUDStyle.dark)
SVProgressHUD.setBackgroundColor(.clear)
SVProgressHUD.setForegroundColor(.white)
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.show()
//SVProgressHUD.show(withStatus: "Loading something, Loading something,Loading something ...")
This will produce same UI output as needed by you in OP. You can find a running sample at my repository (TestPreLoader)

Swift set UIButton setBorderColor in Storyboard

I'm trying to set UIButton Border color on Storyboard:
But the button is still Black. When I try to use layer.borderColor, it doesn't show the border at all.
How to se the color?
Thank you
// UPDATE
I've changed the way to set up based on #dfd to:
import Foundation
import UIKit
#IBDesignable
public class OnboardingButton: UIButton {
#IBInspectable public var borderColor:UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable public var borderWidth:CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var cornerRadius:CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
After set up in Storyboard Custom Class to *OnboardingButton app started build but Faield. There is no error in the Log. Where can I find the error and how to fix it?
Here's my UIButton subclass:
#IBDesignable
public class Button: UIButton {
#IBInspectable public var borderColor:UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable public var borderWidth:CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var cornerRadius:CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
EDIT:
I created a simple project and added the above IBDesignable subclass. Below are three screen shots of the result. Note that in the Identity inspector I've set the Class to be Button, not UIButton and that it reports that the Designables are "Up to date". Note that in the Attributes inspector these appear as properties at the very top.
You can change some properties of UIButton form interface builder but not color of border. Another way would be subclassing it like in answer suggested by dfd.

Setting backgroundColor of custom NSView

What is the process of drawing to NSView using storyboards for osx? I have added a NSView to the NSViewController. Then, I added a few constraints and an outlet.
Next, I added some code to change the color:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var box: NSView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear() {
box.layer?.backgroundColor = NSColor.blueColor().CGColor
//box.layer?.setNeedsDisplay()
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
I would like to do custom drawing and changing colors of the NSView. I have
performed sophisticated drawing on iOS in the past, but am totally stuck here.
Does anyone see what I'm doing wrong?
The correct way is
class ViewController: NSViewController {
#IBOutlet var box: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
}
override func viewWillAppear() {
box.layer?.backgroundColor = NSColor.blue.cgColor
//box.layer?.setNeedsDisplay()
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}}
Swift via property
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(CGColor: colorRef)
} else {
return nil
}
}
set {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.CGColor
}
}
}
Usage:
yourView.backgroundColor = NSColor.greenColor()
Where yourView is NSView or any of its subclasses
Updated for Swift 3
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(cgColor: colorRef)
} else {
return nil
}
}
set {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.cgColor
}
}
}
edit/update:
Another option is to design your own colored view:
import Cocoa
#IBDesignable class ColoredView: NSView {
#IBInspectable var backgroundColor: NSColor = .clear
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
backgroundColor.set()
dirtyRect.fill()
}
}
Then you just need to add a Custom View NSView and set the custom class in the inspector:
Original Answer
Swift 3.0 or later
extension NSView {
var backgroundColor: NSColor? {
get {
guard let color = layer?.backgroundColor else { return nil }
return NSColor(cgColor: color)
}
set {
wantsLayer = true
layer?.backgroundColor = newValue?.cgColor
}
}
}
let myView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
myView.backgroundColor = .red
This works a lot better:
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.blueColor().setFill()
NSRectFill(dirtyRect);
}
Best way to set a NSView background colour in MacOS 10.14 with dark mode support :
1/ Create your colour in Assets.xcassets
2/ Subclass your NSView and add this :
class myView: NSView {
override func updateLayer() {
self.layer?.backgroundColor = NSColor(named: "customControlColor")?.cgColor
}
}
Very simple and dark mode supported with the colour of your choice !
Full guide : https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface
Just one line of code is enough to change the background of any NSView object:
myView.setValue(NSColor.blue, forKey: "backgroundColor")
Instead of this, you can also add an user defined attribute in the interface designer of type Color with keyPath backgroundColor.
Update to Swift 3 solution by #CryingHippo (It showed colors not on every run in my case). I've added DispatchQueue.main.async and now it shows colors on every run of the app.
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(cgColor: colorRef)
} else {
return nil
}
}
set {
DispatchQueue.main.async {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.cgColor
}
}
}
}
None of the solutions is using pure power of Cocoa framework.
The correct solution is to use NSBox instead of NSView. It has always supported fillColor, borderColor etc.
Set titlePosition to None
Set boxType to Custom
Set borderType to None
Set your desired fillColor from asset catalogue (IMPORTANT for dark mode)
Set borderWidth and borderRadius to 0
Bonus:
it dynamically reacts to sudden change of appearance (light to dark)
it supports animations ( no need for dynamic + no need to override animation(forKey key:NSAnimatablePropertyKey) ->)
future macOS support automatically
WARNING:
Using NSBox + system colors in dark mode will apply tinting corectly. If you do not want tinting use catalogue color.
Alternative is to provide subclass of NSView and do the drawing updates in updateLayer
import Cocoa
#IBDesignable
class CustomView: NSView {
#IBInspectable var backgroundColor : NSColor? {
didSet { needsDisplay = true }
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
guard let layer = layer else { return }
layer.backgroundColor = backgroundColor?.cgColor
}
}
Tinting:
Since macOS 10.13 (High Sierra) NSView responds to selector backgroundColor although it is not documented!