I'm trying to put a view with subviews. The view has an image and a text, which are subviews, but when i run the app, it doesn't show the complete text. Here is the code…
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let ballonview = UIImageView()
let label = UILabel()
let turn = 1
ballonview.frame = CGRectZero
label.frame = CGRectZero
label.backgroundColor = UIColor.clearColor()
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = label.font.fontWithSize(20)
let message = UIView()
message.frame = CGRectMake(-160, 48, view.frame.size.width, view.frame.size.height)
message.addSubview(ballonview)
message.addSubview(label)
let text: NSString = "hola como estas"
let size:CGSize = text.boundingRectWithSize(CGSizeMake(240.0, 480.0), options: NSStringDrawingOptions.UsesDeviceMetrics, attributes: [NSFontAttributeName: UIFont.systemFontOfSize(14.0)], context: nil).size
var ballon:UIImage = UIImage()
ballonview.frame = CGRectMake(320.0 - (size.width + 28.0), 2.0, size.width + 28.0, size.height + 15.0)
ballon = UIImage(named:"green.png")!.stretchableImageWithLeftCapWidth(24, topCapHeight: 15)
label.frame = CGRectMake(307.0 - (size.width + 5.0), 8.0, size.width + 5.0, size.height)
ballonview.image = ballon;
label.text = text as String
view.addSubview(message)
}
What can I do?
The problem here is that you are using different font sizes when measuring the size and rendering.
The font of label is 20pt system font, and the font you use for measuring size is 14pt system font. So the size will be smaller than you expected.
Also, the drawing options should be changed to NSStringDrawingUsesLineFragmentOrigin in order to get the correct size if you have characters with long legs (like g, j, y).
Reference: official NSString document.
I'd suggest you change the way you measure the size:
let size: CGSize = text.boundingRectWithSize(
CGSizeMake(240.0, 480.0),
options: .UsesLineFragmentOrigin,
attributes: [
NSFontAttributeName: label.font
],
context: nil
).size
So it will match the font you use for rendering the label.
I hope this answer helps you.
Related
I'm trying to add profile icons via UIImageViews to a UIStackView in order to keep the icons centered in a view. How would I go about adding UIImageViews of a fixed frame to a UIStackView and keep the UIStackView centered in the main view according to varying numbers of UIImageViews in the UIStackView?
let memberIcons: UIStackView = {
let iconView = UIStackView()
iconView.translatesAutoresizingMaskIntoConstraints = false
iconView.axis = .horizontal
iconView.spacing = 5
iconView.distribution = .equalSpacing
iconView.alignment = .center
return iconView
}()
for member in story!.members {
let circle = UIImageView()
circle.frame = CGRect(x: 0, y: 0, width: 36, height: 36)
circle.translatesAutoresizingMaskIntoConstraints = false
circle.layer.cornerRadius = CGFloat(circle.frame.width / 2)
circle.image = member.profilePicture
circle.contentMode = .scaleAspectFill
circle.clipsToBounds = true
memberIcons.addArrangedSubview(circle)
}
Because you set memberIcons.distribution = .equalSpace, the stack view will ask its subviews for their intrinsic sizes. When asked, the UIImage (i.e. circle) will calculate its intrinsic size as "image pixel size / scale", which is not what you want -- you want the image to be of fixed size (36 x 36).
Use Auto Layout on circle:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(memberIcons)
memberIcons.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
memberIcons.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// Limit the stack view's width to no more than 75% of the superview's width
// Adjust as needed
memberIcons.widthAnchor.constraint(lessThanOrEqualTo: view.widthAnchor, multiplier: 0.75).isActive = true
let width: CGFloat = 36.0
for member in story!.members {
// We don't care about the frame here as we're gonna use auto layout
let circle = UIImageView(frame: .zero)
circle.translatesAutoresizingMaskIntoConstraints = false
circle.layer.cornerRadius = width / 2
circle.image = member.profilePicture
circle.contentMode = .scaleAspectFill
circle.clipsToBounds = true
circle.layer.borderWidth = 1
circle.layer.borderColor = UIColor.lightGray.cgColor
memberIcons.addArrangedSubview(circle)
circle.widthAnchor.constraint(equalToConstant: width).isActive = true
circle.heightAnchor.constraint(equalToConstant: width).isActive = true
}
}
Result:
Because we limit the width of the UIStackView, there a maximum number of profile images you can add (7 in this case) before you get a bunch of auto layout error on the console. You can enclose the Stack View inside a Scroll View or use a Collection View for a matrix-like display.
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"))
How can I convert an RGB image into its grayscaled colour space? I can find a lot of code for iOS but non for macOS.. And the Apple's documentations are all in objective C....
let width = image.size.width
let height = image.size.height
let imageRect = NSMakeRect(0, 0, width, height);
let colorSpace = CGColorSpaceCreateDeviceGray();
let bits = image.representations.first as! NSBitmapImageRep;
bitmap!.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: nil)
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue);
let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue);
context.draw(image.cgImage!, in : imageRect);// and this line is wrong obviously..
This is what I have got so far..just copy and pasting from the internet.. but I have no idea on how to go further...
I have found an interesting way to do this.. My code are simply copied from the three sources below.
how to create grayscale image from nsimage in swift?
Greyscale Image using COCOA and NSImage
Changing the Color Space of NSImage: The second reply
My Code:
func saveImage(image:NSImage, destination:URL) throws{
let rep = greyScale(image);
var data = rep.representation(using: NSJPEGFileType, properties: [:]);
try data?.write(to: destination);
}
// rgb2gray
func greyScale(image: NSImage) -> NSBitmapImageRep{
let w = image.size.width
let h = image.size.height
let imageRect : NSRect! = NSMakeRect(0,0, w, h);
let colourSpace : ColourSpace! = CGColorSpaceCreateDeviceGray();
let context : CGContext! = CGContext(data: nil, width: Int(w),
height: Int(h), bitsPerComponent: 8,
bytesPerRow: 0, space: colorSpace,
bitmapInfo: CGImageAlphaInfo.none.rawValue);
context.draw(nsImageToCGImage(image: image), in: imageRect);
let greyImage : CGImage! = context.makeImage();
return NSBitmapImageRep(cgImage: greyImage);
}
func nsImageToCGImage(image: NSImage) -> CGImage{
if let imageData = image.tiffRepresentation as NSData! {
let imageSource : CGImageSource! = CGImageSourceCreateWithData(imageData,
nil);
let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil);
return image;
}
return nil;
}
I am still trying to understand the principle behind.
You can try CIFilter. The annoyance is that you have to convert back and forth between NSImage and CIImage:
import Cocoa
import CoreImage
let url = Bundle.main.url(forResource: "image", withExtension: "jpg")!
let image = CIImage(contentsOf: url)!
let bwFilter = CIFilter(name: "CIColorControls", withInputParameters: ["inputImage": image, "inputSaturation": 0.0])!
if let ciImage = bwFilter.outputImage {
let rep = NSCIImageRep(ciImage: ciImage)
let nsImage = NSImage(size: rep.size)
nsImage.addRepresentation(rep)
// nsImage is now your black-and-white image
}
Image of the pixelated text in the UITextView
Does anyone have any suggestions? The image of the issue is in the clickable link above.
Code:
struct Views {
static var name_field: UITextView?
}
In the viewDidLoad()
Views.name_field = UITextView(frame: CGRectMake(0, 0, name_field_width, 50))
Views.name_field!.textAlignment = NSTextAlignment.Center
Views.name_field!.font = UIFont.systemFontOfSize(15)
Views.name_field!.autocorrectionType = UITextAutocorrectionType.No
Views.name_field!.keyboardType = UIKeyboardType.Default
Views.name_field!.returnKeyType = .Done
Views.name_field!.delegate = self
Calling this function to style it
styleIt(Views.name_field!)
Adds a bottom border style and then sets the font, etc.
func styleIt(target: UITextView){
target.layer.backgroundColor = UIColor.clearColor().CGColor
let _border = CAShapeLayer()
_border.backgroundColor = UIColor.whiteColor().CGColor
_border.frame = CGRectMake(0, CGRectGetHeight(target.frame) - 1.0, CGRectGetWidth(target.frame), 1.0)
_border.shadowColor = UIColor.whiteColor().CGColor
_border.shadowOffset = CGSize(width: 3, height: 3)
_border.shadowOpacity = 0.23
_border.shadowRadius = 4
target.layer.addSublayer(_border)
target.font = UIFont(name: "ClementePDaa-Hairline", size: 24)
target.textColor = UIColor.whiteColor()
target.textContainerInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
applyPlaceholderStyle(target, placeholderText: _SEARCH_TEXT)
target.returnKeyType = .Done
target.frame = CGRectIntegral(target.frame)
target.layer.shouldRasterize = true
_border.shouldRasterize = true
target.textInputView.layer.shouldRasterize = true
}
This UITextView is a subview of search_field which is simply a UIView
search_field!.addSubview(Views.name_field!)
Your text view is blurry because the frame is using floating numbers.
To force integers value for your frame just do :
textView.frame = CGRectIntegral(textView.frame)
I would like to create a pdf dynamically. But I have problems with new pages.
Currently I am writing a text block and if that text block does not fit to the remaining Y position - I write the whole text block on a new page.
But I would like to take the whole text block, write to the actual page as much as I can and write the rest on a new page. Does someone know how to split the text in a rect depending on the rect size?
Thats the code:
writeString ist coming from CoreData and has a dynamic length.
func drawTextBlock(pdfFileName: String, writeString: NSString = " ", fontSize: CGFloat = 12.0, fontName: String = "Helvetica", textAlignment: NSTextAlignment = NSTextAlignment.Natural) {
//Text attribute configuration
let font = UIFont(name: fontName, size: fontSize)
let textStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
textStyle.alignment = textAlignment
let textFontAttributes = [
NSFontAttributeName: font!,
NSParagraphStyleAttributeName: textStyle
]
let textSize = writeString.sizeWithAttributes(textFontAttributes)
let linesNeeded = ceil(textSize.width / (myPageCalibration.pageWidth - myPageCalibration.xOffsetLeft - myPageCalibration.xOffsetRight))
if remainingSpaceY >= (textSize.height * linesNeeded) {
//draw text in rectangle
writeString.drawInRect(CGRectMake(myPageCalibration.xOffsetLeft, myPageCalibration.yOffsetUp + currentPositionY, myPageCalibration.pageWidth - myPageCalibration.xOffsetLeft - myPageCalibration.xOffsetRight, (textSize.height * linesNeeded )), withAttributes: textFontAttributes)
//calc remainingSpace
currentPositionY = CGFloat(linesNeeded * textSize.height) + currentPositionY
remainingSpaceY = remainingSpaceY - (textSize.height * linesNeeded)
}
else {
//next page
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 595, 842), nil);
remainingSpaceY = myPageCalibration.pageHeight - myPageCalibration.yOffsetDown - myPageCalibration.yOffsetUp
currentPositionY = 0
drawTextBlock(pdfFileName, writeString: writeString, fontSize: fontSize, fontName: fontName, textAlignment: textAlignment)
}
}