Related
There is a page with several elements:
ImageView
TextView - for the title
Button
TextView - for description
Button
All these elements are in ScrollView.
Question: how to make it so that when editing text from TextView, the page turns to where the cursor is located?
Since the size of the TextView depends on the size of the user's text, I need to disable scrolling in the TextView. And then I don't know how to scroll the page to where the cursor is.
I haven't done much yet, I can't figure out how to do it…
My Code:
import UIKit
class DreamPageViewController: UIViewController {
private let dreamTasks = DreamTasksViewController()
private let scrollView = UIScrollView()
private let contentView = UIView()
private var cancelExecutionDream = false
private let headerImage: UIImageView = {
let image = UIImageView(image: UIImage(systemName: "multiply.square.fill"))
image.clipsToBounds = true
image.layer.cornerRadius = 40
image.contentMode = .scaleAspectFill
image.backgroundColor = .blue
image.tintColor = .systemBlue
return image
}()
private let dreamName: UITextView = {
let title = UITextView()
//label.numberOfLines = 0
title.isScrollEnabled = false
title.text = "Title"
title.font = .systemFont(ofSize: 23, weight: .bold)
return title
}()
private let doneButton: UIButton = {
let button = UIButton()
button.layer.cornerRadius = 20
button.layer.borderWidth = 2.5
button.layer.borderColor = UIColor.systemGray3.cgColor
button.setTitle("Выполнить", for: .normal)
button.setTitleColor(.systemGray2, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20.0, weight: .semibold)
button.addTarget(self, action: #selector(DoneButtonAction), for: .touchUpInside)
return button
}()
private let textDreamPage: UITextView = {
let text = UITextView()
text.font = .systemFont(ofSize: 18, weight: .medium)
text.textColor = .darkGray
text.isScrollEnabled = false
//text.numberOfLines = 0
text.text = "Lots of text... "
return text
}()
private let tasksButton: UIButton = {
let button = UIButton()
button.backgroundColor = .blue
button.layer.cornerRadius = 20
button.addTarget(self, action: #selector(dreamTasksAction), for: .touchUpInside)
return button
}()
private let taskButtonTitle: UILabel = {
let label = UILabel()
label.text = "Задачи"
label.textColor = .white
label.font = .systemFont(ofSize: 23, weight: .semibold)
return label
}()
private let tasksCountButton: UIButton = {
let button = UIButton()
let taskCount = DreamTasksViewController()
button.setTitle(String(taskCount.tasks.count), for: .normal)
button.tintColor = .white
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
setupUI()
setupScrollView()
componentsConfigure()
}
#objc func DoneButtonAction(sender: UIButton!) {
let animation = ButtonAnimation()
if cancelExecutionDream == false {
doneButton.setTitle("Выполнено!", for: .normal)
doneButton.layer.borderWidth = 0
doneButton.backgroundColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)
doneButton.setTitleColor(.white, for: .normal)
animation.animationButton(doneButton)
cancelExecutionDream.toggle()
} else if cancelExecutionDream == true {
doneButton.layer.borderWidth = 2.5
doneButton.layer.borderColor = UIColor.systemGray3.cgColor
doneButton.setTitle("Выполнить", for: .normal)
doneButton.backgroundColor = .none
doneButton.setTitleColor(.systemGray2, for: .normal)
animation.animationButton(doneButton)
cancelExecutionDream.toggle()
} else { return }
}
#objc func dreamTasksAction(sender: UIButton) {
dreamTasks.title = "Задачи"
navigationController?.pushViewController(dreamTasks, animated: true)
}
private func setupUI() {
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.backButtonTitle = " "
}
private func setupScrollView() {
view.addSubview(scrollView)
scrollView.addSubview(contentView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: -50).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -10).isActive = true
}
private func componentsConfigure() {
contentView.addSubview(headerImage)
contentView.addSubview(dreamName)
contentView.addSubview(doneButton)
contentView.addSubview(textDreamPage)
contentView.addSubview(tasksButton)
tasksButton.addSubview(taskButtonTitle)
tasksButton.addSubview(tasksCountButton)
[headerImage, dreamName, doneButton, textDreamPage, tasksButton, taskButtonTitle, tasksCountButton].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
NSLayoutConstraint.activate([
headerImage.topAnchor.constraint(equalTo: contentView.topAnchor),
headerImage.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 7),
headerImage.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -7),
headerImage.heightAnchor.constraint(greaterThanOrEqualTo: view.heightAnchor, multiplier: 3.3/5),
dreamName.topAnchor.constraint(equalTo: headerImage.bottomAnchor, constant: 10),
dreamName.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 10),
dreamName.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -10),
dreamName.heightAnchor.constraint(equalToConstant: 40),
doneButton.topAnchor.constraint(equalTo: dreamName.bottomAnchor, constant: 10),
doneButton.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 10),
doneButton.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -10),
doneButton.heightAnchor.constraint(equalToConstant: 60),
textDreamPage.topAnchor.constraint(equalTo: doneButton.bottomAnchor, constant: 10),
textDreamPage.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 10),
textDreamPage.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -10),
textDreamPage.bottomAnchor.constraint(equalTo: tasksButton.topAnchor, constant: -10),
tasksButton.topAnchor.constraint(equalTo: textDreamPage.bottomAnchor, constant: 25),
tasksButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
tasksButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
tasksButton.heightAnchor.constraint(equalToConstant: 70),
tasksButton.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.95/1),
taskButtonTitle.centerYAnchor.constraint(equalTo: tasksButton.centerYAnchor),
taskButtonTitle.leftAnchor.constraint(equalTo: tasksButton.leftAnchor, constant: 15),
tasksCountButton.heightAnchor.constraint(equalToConstant: 20),
tasksCountButton.centerYAnchor.constraint(equalTo: tasksButton.centerYAnchor),
tasksCountButton.rightAnchor.constraint(equalTo: tasksButton.rightAnchor, constant: -15)
])
}
}
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 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
}
I am learning auto layout in iOS8 by using SnapKit . I got lot of errors while applying constraints to cell subviews. below is the code used as subview to cell.contentview.
class FanFeedDynamicCellView: UIView{
var fanProfileImageView:UIImageView?
var fanNameLabel:UILabel?
var contentLabel:UILabel?
var thumbnailImageView:UIImageView?
var spacierView_FanProfile:UIView?
override init(frame: CGRect) {
super.init(frame : frame)
setupViewProperties()
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
func setupViewProperties()
{
//1 add fanProfileImageView
fanProfileImageView = UIImageView()
fanProfileImageView!.image = UIImage(named: "avatar")
// setBorder(fanProfileImageView!)
self.addSubview(fanProfileImageView!)
//2 add Fan Name Label
fanNameLabel = UILabel()
fanNameLabel!.lineBreakMode = .ByTruncatingTail
fanNameLabel!.numberOfLines = 1
fanNameLabel!.textAlignment = .Left
fanNameLabel!.textColor = UIColor.blackColor()
fanNameLabel!.backgroundColor = UIColor(red: 0, green: 0, blue: 1, alpha: 0.1) // light blue
self.addSubview(fanNameLabel!)
//3 add ContentLabel
contentLabel = UILabel()
contentLabel!.lineBreakMode = .ByTruncatingTail
contentLabel!.numberOfLines = 0
contentLabel!.textAlignment = .Left
contentLabel!.textColor = UIColor.darkGrayColor()
contentLabel!.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.1) // light red
self.addSubview(contentLabel!)
//4 add Thumbnail View
thumbnailImageView = UIImageView()
// setBorder(thumbnailImageView!)
thumbnailImageView!.contentMode = .ScaleAspectFit
thumbnailImageView!.image = UIImage(named: "avatar")
self.addSubview(thumbnailImageView!)
updateFonts()
//Constraints for subviews
//setupConstraintsForProperties()
}
func updateFonts()
{
fanNameLabel!.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
contentLabel!.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption2)
}
override func updateConstraints()
{
let padding:UIEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10)
fanProfileImageView!.snp_makeConstraints { (make) -> Void in
make.top.equalTo(self.snp_top).offset(padding.top)
make.left.equalTo(self.snp_left).offset(padding.left)
make.width.height.equalTo(60.0)
make.bottom.lessThanOrEqualTo(thumbnailImageView!.snp_top).offset(-padding.bottom)
}
fanNameLabel!.snp_makeConstraints { (make) -> Void in
make.top.equalTo(self.snp_top).offset(padding.top)
make.left.equalTo(fanProfileImageView!.snp_right).offset(padding.right)
make.right.equalTo(self.snp_right).offset(-padding.right)
// make.bottom.lessThanOrEqualTo(contentLabel!.snp_top).offset(-padding.top)
make.height.equalTo(20)
}
contentLabel!.snp_makeConstraints { (make) -> Void in
make.top.equalTo(fanNameLabel!.snp_bottom).offset(padding.top)
make.left.equalTo(fanProfileImageView!.snp_right).offset(padding.left)
make.right.equalTo(self.snp_right).offset(-padding.right)
// make.bottom.lessThanOrEqualTo(thumbnailImageView!.snp_top).offset(-padding.bottom)
// make.height.greaterThanOrEqualTo(20)
}
thumbnailImageView!.snp_makeConstraints { (make) -> Void in
make.top.greaterThanOrEqualTo(contentLabel!.snp_bottom).offset(padding.top)
// make.left.equalTo(padding.left)
make.bottom.lessThanOrEqualTo(-padding.bottom)
make.height.greaterThanOrEqualTo(20)
make.centerX.equalTo(self.snp_centerX) }
super.updateConstraints()
}
func setBorder(cView:UIView) -> UIView
{
let cLayer : CALayer = cView.layer
cLayer.borderColor = UIColor.redColor().CGColor
cLayer.borderWidth = 0.5
return cView
}
override func layoutSubviews() {
super.layoutSubviews()
fanNameLabel!.contentHuggingPriorityForAxis(.Vertical)
fanNameLabel!.contentCompressionResistancePriorityForAxis(.Vertical)
contentLabel!.contentHuggingPriorityForAxis(.Vertical)
contentLabel!.contentCompressionResistancePriorityForAxis(.Vertical)
}
The output would be the same as attached image . here we use on profile Image in LeftSide . User Name on top of label. Content label marked in light orange color would be the multiline. below this i attached the ImageView. when i scroll the tableview the height of the cell is unpredictable(layout changes automatically). would help me to correct the constraint to achieve the this output. For first launch the multiline cell would be in One line . once i goes to invisible the come again visible it adopt for full label content
You add more constraints than you had to. Also you don't really need to use contentHuggingPriority and contentCompressionResistance to achieve what you want.
This is how to make it work:
FanFeedDynamicCellView
class FanFeedDynamicCellView: UIView {
let fanProfileImageView = UIImageView()
let fanNameLabel = UILabel()
let contentLabel = UILabel()
let thumbnailImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
setupConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
fanProfileImageView.image = UIImage(named: "avatar")
addSubview(fanProfileImageView)
fanNameLabel.lineBreakMode = .ByTruncatingTail
fanNameLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
fanNameLabel.numberOfLines = 1
fanNameLabel.textAlignment = .Left
fanNameLabel.textColor = UIColor.blackColor()
fanNameLabel.backgroundColor = UIColor(red: 0, green: 0, blue: 1, alpha: 0.1) // light blue
addSubview(fanNameLabel)
contentLabel.lineBreakMode = .ByTruncatingTail
contentLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption2)
contentLabel.numberOfLines = 0
contentLabel.textAlignment = .Left
contentLabel.textColor = UIColor.darkGrayColor()
contentLabel.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.1) // light red
addSubview(contentLabel)
thumbnailImageView.contentMode = .ScaleAspectFit
thumbnailImageView.image = UIImage(named: "thumbnail.jpg")
thumbnailImageView.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 0.2) // light green
addSubview(thumbnailImageView)
}
func setupConstraints() {
let padding:UIEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10)
fanProfileImageView.snp_makeConstraints { (make) -> Void in
make.top.equalTo(padding.top)
make.left.equalTo(padding.left)
make.width.height.equalTo(60)
make.bottom.lessThanOrEqualTo(-padding.bottom)
}
fanNameLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(fanProfileImageView)
make.left.equalTo(fanProfileImageView.snp_right).offset(padding.right)
make.right.equalTo(-padding.right)
}
contentLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(fanNameLabel.snp_bottom).offset(padding.top)
make.left.right.equalTo(fanNameLabel)
}
thumbnailImageView.snp_makeConstraints { (make) -> Void in
make.top.greaterThanOrEqualTo(contentLabel.snp_bottom).offset(padding.top)
make.top.greaterThanOrEqualTo(fanProfileImageView.snp_bottom).offset(padding.top)
make.left.equalTo(fanProfileImageView)
make.right.equalTo(fanNameLabel)
make.bottom.equalTo(-padding.bottom)
}
}
}
I don't know why you created a UIView subclass instead of a UITableViewCell subclass for this, but I kept it that way and just added this view to a custom UITableViewCell subclass
FanFeedTableViewCell
This only adds the FanFeedDynamicCellView to its contentView:
class FanFeedTableViewCell: UITableViewCell {
let fanFeedView = FanFeedDynamicCellView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
contentView.addSubview(fanFeedView)
fanFeedView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(contentView)
}
}
}
And that's it! Now just use this FanFeedTableViewCell in your UITableView and set the texts and images in cellForRowAtIndexPath.
Don't forget to do this with your table view to enable dynamic cell heights:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100 // or whatever your estimated row height is. This does not have to be precise
This is how it looks:
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
}
}