I am trying to add a search/filter option to a navigation bar in Swift 3. Currently, I am pushing to the current UICollectionView from previous views, all of which are on a NavigationViewController stack.
In the current UICollectionViewController, I have set up a UISearchBar object like so:
let searchBar: UISearchBar = {
let sb = UISearchBar()
sb.placeholder = "Enter search term"
return sb
}()
And inside collectionViewController, viewdidload() I set up the following:
// searchbar
let navBar = self.navigationController?.navigationBar
navigationController?.navigationBar.addSubview(searchBar)
searchBar.anchor(top: navBar?.topAnchor, left: navBar?.leftAnchor , bottom: navBar?.bottomAnchor, right: navBar?.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0 )
The anchoring is done through a custom extension, though I do not believe it effects anything, but is as follows:
extension UIView{
// anchor
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?,paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat){
translatesAutoresizingMaskIntoConstraints = false
if let top = top{
self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left{
self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom{
bottom.constraint(equalTo: bottom, constant: paddingBottom).isActive = true
}
if let right = right{
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0{
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0{
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
}
However, when I load the corresponding uicollectionviewcontrollers, I am not able to see the searchbar in the navigationController navBar area. Is there something else that needs to be set in order for it to be displayed?
You have to specify a frame for your UISearchBar. Try this:
let searchBar: UISearchBar = {
let sb = UISearchBar(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 44)) //change the width & height according to your needs.
sb.placeholder = "Enter search term"
return sb
}
Related
I am subclassing an NSPopUpButton with the purpose of having control over the drawing methods of the button itself, but also the NSMenu that will pop up. Therefore I am also subclassing NSMenu and - most importantly - setting the view of each menu item to a custom NSView.
So far I have managed to come very close to the appearance of the original NSPopupButton and its menu. In the code, I provide a small window that will display an original NSButton on the left side and an instance of my custom version on the right.
However, the custom menu does not function properly. The following issues occur:
The button can be clicked and the menu will pop up. When the mouse is moved inside the menu, the item on which the pointer is hovering will highlight properly, except for the item that is selected: when the mouse exits its tracking area to the neighboring item, this one will be highlighted, but the first one will not lose highlight color. Only when entering the selected item again and then exiting it a second time it will lose the highlight properly.
Clicking an item will NOT dismiss the menu, the menu does not respond to any click within one of its items. The menu will however be dismissed when a click outside the menu occurs.
The button and the menu are fully functional when using the keyboard: Tab switches between the standard and the custom PopUpButton, space will summon the menu, the arrow buttons move the selection, and space or return will make a selection and dismiss the menu.
The first menu entry (Item 1) can not be selected, when dismissing the menu with enter or space when Item 1 is highlighted the Item that was selected before will stay selected.
Problem 4 is possibly unrelated, my main question is:
Why do the CustomMenuItemViews not respond to mouse events the way a stock Menu does? I assume that there is either a method that I have to override, a delegate that has to be set somewhere, or both, but I have not yet managed to find the part of the code where I have to hook in.
I was at least able to pinpoint the problem to the overridden method willOpenMenu - if I do not override, I get normal behavior, but of course, the menu will then be drawn by the cocoa method.
import Cocoa
import AppKit
#main
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
window.contentViewController = MyViewController(size: NSSize(width: 200, height: 80))
}
}
class MyViewController: NSViewController {
public init(size: NSSize) {
super.init(nibName: nil, bundle: nil)
self.view = MyInnerView(frame: NSRect(x: 0, y: 0, width: size.width, height: size.height))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MyInnerView: NSView, NSMenuDelegate {
public override init(frame: NSRect) {
super.init(frame: frame)
let standardPopUp = NSPopUpButton(title: "Switch", target: nil, action: nil)
standardPopUp.frame = NSRect(x: 10, y: constant.buttonFrameY, width: 80, height: constant.buttonFrameHeigth)
standardPopUp.addItems(withTitles: ["Item 1", "Item 2", "Item 3"])
let popUpCell = CustomPopUpButtonCell()
let customPopUp = CustomPopUpButton(title: "Switch", target: nil, action: nil)
customPopUp.cell = popUpCell
customPopUp.menu = CustomPopUpMenu()
customPopUp.menu?.delegate = self
customPopUp.frame = NSRect(x: 90, y: constant.buttonFrameY, width: 80, height: constant.buttonFrameHeigth)
customPopUp.addItems(withTitles: ["Item 1", "Item 2", "Item 3"])
self.addSubview(standardPopUp)
self.addSubview(customPopUp)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CustomPopUpButton: NSPopUpButton {
override func drawFocusRingMask() {
// prevent focus ring drawing
}
override func becomeFirstResponder() -> Bool {
(self.cell as! CustomPopUpButtonCell).hasFocus = true
self.needsDisplay = true
return true
}
override func resignFirstResponder() -> Bool {
(self.cell as! CustomPopUpButtonCell).hasFocus = false
self.needsDisplay = true
return true
}
// this function breaks the intended behaviour
override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
for (index,item) in self.menu!.items.enumerated() {
item.view = MenuItemCustomView(frame: NSRect(x: 0, y: 0, width: 150, height: constant.popUpMenuCellHeigth))
item.view?.menu = menu
(item.view as! MenuItemCustomView).text = item.title
if self.indexOfSelectedItem == index {
(item.view as! MenuItemCustomView).selected = true
}
}
}
}
class CustomPopUpMenu: NSMenu {
}
class CustomPopUpButtonCell: NSPopUpButtonCell {
var hasFocus = false
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
let context = NSGraphicsContext.current!.cgContext
// calculate width
let buttonWidth = CGFloat(60)
// draw rounded rect with shadow
let buttonRect = CGRect(x: constant.popUpButtonInset, y: (cellFrame.height/2 - constant.popUpButtonHeigth/2) - constant.popUpButtonVerticalOffset, width: buttonWidth, height: constant.popUpButtonHeigth)
let roundedRect = CGPath.init(roundedRect: buttonRect, cornerWidth: constant.popUpButtonCornerRadius, cornerHeight: constant.popUpButtonCornerRadius, transform: nil)
let shadowColor = CGColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.5)
context.setShadow(
offset: CGSize(width: 0, height: 0),
blur: 3.0,
color: shadowColor)
context.setLineWidth(3)
context.setFillColor(.white)
context.addPath(roundedRect)
context.fillPath()
context.setShadow(offset: CGSize(), blur: 0)
// draw arrow rect
let arrowRect = CGRect(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth - constant.popUpButtonArrowRectGap, y: (cellFrame.height/2 - constant.popUpButtonArrowRectWidth/2 - constant.popUpButtonVerticalOffset), width: constant.popUpButtonArrowRectWidth, height: constant.popUpButtonArrowRectWidth)
let arrowRoundedRect = CGPath.init(roundedRect: arrowRect, cornerWidth: constant.popUpButtonArrowRectCornerRadius, cornerHeight: constant.popUpButtonArrowRectCornerRadius, transform: nil)
context.setFillColor(NSColor.controlAccentColor.cgColor)
context.addPath(arrowRoundedRect)
context.fillPath()
// draw arrows
context.setStrokeColor(.white)
context.setLineWidth(1.5)
context.setLineCap(.round)
context.move(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth - constant.popUpButtonArrowRectGap + 5, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset + 2)))
context.addLine(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth/2 - constant.popUpButtonArrowRectGap, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset + constant.popUpButtonArrowRectWidth/2 - 3)))
context.addLine(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectGap - 5, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset + 2)))
context.strokePath()
context.move(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth - constant.popUpButtonArrowRectGap + 5, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset - 2)))
context.addLine(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectWidth/2 - constant.popUpButtonArrowRectGap, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset - constant.popUpButtonArrowRectWidth/2 + 3)))
context.addLine(to: CGPoint(x: constant.popUpButtonInset + buttonWidth - constant.popUpButtonArrowRectGap - 5, y: (cellFrame.height/2 - constant.popUpButtonVerticalOffset - 2)))
context.strokePath()
// draw text
let textColor: NSColor = .black
let attributes = [
NSAttributedString.Key.font : NSFont(name: "Lucida Grande", size: CGFloat(12)),
NSAttributedString.Key.foregroundColor : textColor
]
let textPosition = NSPoint(x: constant.popUpButtonInset + constant.popUpButtonArrowRectGap, y: constant.popUpButtonVerticalOffset + 8 - constant.popUpButtonArrowRectGap)
NSAttributedString(string: self.selectedItem!.title, attributes: attributes as [NSAttributedString.Key : Any]).draw(at: textPosition)
if hasFocus {
let buttonRect = CGRect(x: constant.popUpButtonInset - constant.popUpButtonFocusRingThickness/4, y: (cellFrame.height/2 - constant.popUpButtonHeigth/2) - constant.popUpButtonVerticalOffset - constant.popUpButtonFocusRingThickness/4, width: buttonWidth + constant.popUpButtonFocusRingThickness*0.5, height: constant.popUpButtonHeigth + constant.popUpButtonFocusRingThickness*0.5)
let roundedRect = CGPath.init(roundedRect: buttonRect, cornerWidth: constant.popUpButtonFocusRingCornerRadius, cornerHeight: constant.popUpButtonFocusRingCornerRadius, transform: nil)
context.setLineWidth(constant.popUpButtonFocusRingThickness)
context.setStrokeColor((NSColor.keyboardFocusIndicatorColor).cgColor)
context.addPath(roundedRect)
context.strokePath()
}
}
}
class MenuItemCustomView: NSView {
var text: String = ""
var scaleFactor: CGFloat = 1
var selected = false
override func draw(_ dirtyRect: NSRect) {
let context = NSGraphicsContext.current!.cgContext
context.setLineWidth(1)
var textColor: NSColor
if self.enclosingMenuItem!.isHighlighted {
textColor = .white
context.setStrokeColor(.white
)
// draw selection frame
let arrowRect = CGRect(x: constant.popUpMenuSelectionInset, y: 0, width: (self.frame.width - constant.popUpMenuSelectionInset*2), height: self.frame.height)
let arrowRoundedRect = CGPath.init(roundedRect: arrowRect, cornerWidth: constant.popUpButtonArrowRectCornerRadius, cornerHeight: constant.popUpButtonArrowRectCornerRadius, transform: nil)
context.setFillColor(NSColor.controlAccentColor.cgColor)
context.addPath(arrowRoundedRect)
context.fillPath()
} else {
textColor = .black
context.setStrokeColor(.black)
}
let attributes = [
NSAttributedString.Key.font : NSFont(name: "Lucida Grande", size: CGFloat(12*scaleFactor)),
NSAttributedString.Key.foregroundColor : textColor
]
let textPosition = NSPoint(x: constant.popUpMenuTextX*scaleFactor, y: constant.popUpMenuTextY*scaleFactor)
NSAttributedString(string: self.text, attributes: attributes as [NSAttributedString.Key : Any]).draw(at: textPosition)
if selected {
// draw checkmark
context.setLineWidth(2*scaleFactor)
let inset = constant.popUpMenuSelectionInset
context.move(to: CGPoint(x: (inset + 3)*scaleFactor, y: (self.frame.height/2)))
context.addLine(to: CGPoint(x: (inset + 7)*scaleFactor, y: self.frame.height*0.3))
context.addLine(to: CGPoint(x: (inset + 13)*scaleFactor, y: (self.frame.height*0.7)))
context.strokePath()
}
}
}
struct constant {
static let popUpButtonHeigth = CGFloat(20)
static let popUpButtonInset = CGFloat(4)
static let popUpButtonCornerRadius = CGFloat(5)
static let popUpButtonVerticalOffset = CGFloat(1.5)
static let popUpButtonFocusRingThickness = CGFloat(4)
static let popUpButtonFocusRingCornerRadius = CGFloat(6)
static let popUpButtonArrowRectWidth = CGFloat(15)
static let popUpButtonArrowRectGap = CGFloat(2)
static let popUpButtonArrowRectCornerRadius = CGFloat(3)
static let popUpMenuCellHeigth = CGFloat(24)
static let popUpMenuTextX = CGFloat(25)
static let popUpMenuTextY = CGFloat(4)
static let popUpMenuSelectionInset = CGFloat(5)
static let buttonFrameY = CGFloat(10)
static let buttonFrameHeigth = CGFloat(35)
}
I am aiming to make a program in which I am using using SwiftUI buttons to update by SCNView in SceneKit. I have a cylinder as a SCNCylinder in my SCNView inside a frame in SwiftUI. I want my cylinder to rotate about 180° after I press the button. In my current code I have used #State and #Binding to update the view. But somehow the cylinder rotates as soon as I run the code, not waiting for me to touch the button. Not sure why this happens
Here is my code :
struct ContentView: View {
#State var rotationAngle: Float = 180
var body: some View {
VStack{
Button(action: {
// What to perform
self.rotationAngle = 180
}) {
// How the button looks like
Text("180°")
.frame(width: 100, height: 100)
.position(x: 225, y: 500)
}
SceneKitView(angle: self.$rotationAngle)
.frame(width: 300, height: 300)
.position(x: 225, y: 0)
}
}
}
struct SceneKitView: UIViewRepresentable {
#Binding var angle: Float
func degreesToRadians(_ degrees: Float) -> CGFloat {
return CGFloat(degrees * .pi / 180)
}
func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
let sceneView = SCNView()
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = UIColor.white
sceneView.frame = CGRect(x: 0, y: 10, width: 0, height: 1)
return sceneView
}
func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
let cylinder = SCNCylinder(radius: 0.02, height: 2.0)
let cylindernode = SCNNode(geometry: cylinder)
cylindernode.position = SCNVector3(x: 0, y: 0, z: 0)
cylinder.firstMaterial?.diffuse.contents = UIColor.green
cylindernode.pivot = SCNMatrix4MakeTranslation(0, -1, 0)
let rotation = SCNAction.rotate(by: self.degreesToRadians(self.angle), around: SCNVector3(1, 0, 0), duration: 5)
sceneView.scene?.rootNode.addChildNode(cylindernode)
cylindernode.runAction(rotation)
}
typealias UIViewType = SCNView
}
I want the cylinder to rotate after I press the button. Please help me with this problem.
just set startingAngle to 0
#State var rotationAngle: Float = 0
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.
I have migrated my app from Swift 3.1 to Swift 4.0 (Xcode 8.3.3 to Xcode 9.0) and some part of my interface is broken now. Navigation Bar of Navigation controller is complete mess. Please, look at screenshot:
There are 3 elements:
left Netfnet logo (image)
right Signal strength (image)
right QR Code button
As you can see, two images are too big and not in center and button was deformed (it should be perfect square, all images too).
There is code which generated navigation controller:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
settings()
}
func settings() {
let color = UIColor(red: 81 / 255, green: 155 / 255, blue: 22 / 255, alpha: 1.0)
self.navigationController?.navigationBar.barTintColor = color
let logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
logoImageView.contentMode = .scaleAspectFit
let logo = UIImage(named: "littleLogoImage")
logoImageView.image = logo
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: logoImageView)
let signalStengthImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
signalStengthImageView.contentMode = .scaleAspectFit
signalStengthImageView.image = UIImage(named: "signalStrength4")
let signalStengthImageItem = UIBarButtonItem(customView: signalStengthImageView)
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "qrCodeButton"), for: .normal)
button.frame = CGRect(x: 0, y: 0, width: 35, height: 35)
let qrCodeButtonItem = UIBarButtonItem(customView: button)
navigationItem.rightBarButtonItems = [qrCodeButtonItem, signalStengthImageItem] //
}
}
I can decrease resolution of images directly myself, but I just don't get why everting was fine in Swift 3.1 and in Swift 4.0 is broken.
I will be thankful for any help or advice.
You have to add width and height constraints.
Your barImageView and barButton in CustomNavigationController should be like below :
func barImageView(imageName: String) -> UIBarButtonItem {
let imgView = imageView(imageName: imageName)
let widthConstraint = imgView.widthAnchor.constraint(equalToConstant: 35)
let heightConstraint = imgView.heightAnchor.constraint(equalToConstant: 35)
heightConstraint.isActive = true
widthConstraint.isActive = true
return UIBarButtonItem(customView: imgView)
}
func barButton(imageName: String, selector: Selector) -> UIBarButtonItem {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: imageName), for: .normal)
button.frame = CGRect(x: 0, y: 0, width: 35, height: 35)
button.addTarget(self, action: selector, for: .touchUpInside)
let widthConstraint = button.widthAnchor.constraint(equalToConstant: 35)
let heightConstraint = button.heightAnchor.constraint(equalToConstant: 35)
heightConstraint.isActive = true
widthConstraint.isActive = true
return UIBarButtonItem(customView: button)
}
Your signalStengthImageView in LogoWithSignalStrength:
signalStengthImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
signalStengthImageView.contentMode = .scaleAspectFit
let widthConstraint = signalStengthImageView.widthAnchor.constraint(equalToConstant: 35)
let heightConstraint = signalStengthImageView.heightAnchor.constraint(equalToConstant: 35)
heightConstraint.isActive = true
widthConstraint.isActive = true
In Xcode 9, Navigation bar items are constraints base, Add this:
if #available(iOS 11.0, *) {
logoImageView.widthAnchor.constraint(equalToConstant: 35).isActive = true
logoImageView.heightAnchor.constraint(equalToConstant: 35).isActive = true
} else {
//set frames
}
How can I have the text scale to fit the bounds I gave it?
I've done something like this in the past.
-(void)calcFontSizeToFitRect:(NSRect)r {
float targetWidth = r.size.width - xMargin;
float targetHeight = r.size.height - yMargin;
// the strategy is to start with a small font size and go larger until I'm larger than one of the target sizes
int i;
for (i=minFontSize; i<maxFontSize; i++) {
NSDictionary* attrs = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont fontWithName:currentFontName size:i], NSFontAttributeName, nil];
NSSize strSize = [stringValue sizeWithAttributes:attrs];
[attrs release];
if (strSize.width > targetWidth || strSize.height > targetHeight) break;
}
[self setCurrentFontSize:(i-1)];
}
The stringValue variable is the text you want sized. The xMargin and yMargin variables are for spacing that you want. The minFontSize and maxFontSize variables give limits to how small or large you want to go.
This solution appropriated from iOS works quite well. However, one thing to note: If you are setting this up programatically, you need to initialise your NSTextfield with a rect that has a width and height, otherwise the bounds returns 0.
Also here's the link where I found this solution:
https://medium.com/#joncardasis/dynamic-text-resizing-in-swift-3da55887beb3
extension NSFont {
/**
Will return the best font conforming to the descriptor which will fit in the provided bounds.
*/
static func bestFittingFontSize(for text: String, in bounds: CGRect, fontDescriptor: NSFontDescriptor, additionalAttributes: [NSAttributedString.Key: Any]? = nil) -> CGFloat {
let constrainingDimension = min(bounds.width, bounds.height)
let properBounds = CGRect(origin: .zero, size: bounds.size)
var attributes = additionalAttributes ?? [:]
let infiniteBounds = CGSize(width: CGFloat.infinity, height: CGFloat.infinity)
var bestFontSize: CGFloat = constrainingDimension
for fontSize in stride(from: bestFontSize, through: 0, by: -1) {
let newFont = NSFont(descriptor: fontDescriptor, size: fontSize)
attributes[.font] = newFont
let currentFrame = text.boundingRect(with: infiniteBounds, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes, context: nil)
if properBounds.contains(currentFrame) {
bestFontSize = fontSize
break
}
}
return bestFontSize
}
static func bestFittingFont(for text: String, in bounds: CGRect, fontDescriptor: NSFontDescriptor, additionalAttributes: [NSAttributedString.Key: Any]? = nil) -> NSFont {
let bestSize = bestFittingFontSize(for: text, in: bounds, fontDescriptor: fontDescriptor, additionalAttributes: additionalAttributes)
// TODO: Safely unwrap this later
return NSFont(descriptor: fontDescriptor, size: bestSize)!
}
}
extension NSTextField {
/// Will auto resize the contained text to a font size which fits the frames bounds.
/// Uses the pre-set font to dynamically determine the proper sizing
func fitTextToBounds() {
guard let currentFont = font else {
return
}
let text = stringValue
let bestFittingFont = NSFont.bestFittingFont(for: text, in: bounds, fontDescriptor: currentFont.fontDescriptor, additionalAttributes: basicStringAttributes)
font = bestFittingFont
}
private var basicStringAttributes: [NSAttributedString.Key: Any] {
var attribs = [NSAttributedString.Key: Any]()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = self.alignment
paragraphStyle.lineBreakMode = self.lineBreakMode
attribs[.paragraphStyle] = paragraphStyle
return attribs
}
}
For me label.adjustsFontSizeToFitWidth = true reduces the font size.
with...
lazy var labelContainerView: UIView =
{ let view = UIView()
return view.labelContainerView(view: view, label) }()
extension UIView {
func anchor( top: NSLayoutYAxisAnchor?,
left: NSLayoutXAxisAnchor?,
bottom: NSLayoutYAxisAnchor?,
right: NSLayoutXAxisAnchor?,
paddingTop: CGFloat,
paddingLeft: CGFloat,
paddingBottom: CGFloat,
paddingRight: CGFloat,
width: CGFloat,
height: CGFloat )
{ translatesAutoresizingMaskIntoConstraints = false
if let top = top { self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true }
if let left = left { self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true }
if let bottom = bottom { self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true }
if let right = right { self.rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true }
if width != 0 { widthAnchor.constraint(equalToConstant: width).isActive = true }
if height != 0 { heightAnchor.constraint(equalToConstant: height).isActive = true }
}
}
func labelContainerView(view: UIView, _ label: UILabel) -> UIView
{ view.addSubview(label)
label.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
return view
}
}