How to change window frame size without change its location - swift

self.view.window?.frame.size.width = 430
self.view.window?.frame.size.height = 310
The height and width are read-only, so seems the only way to change the dimension of a frame is to re-assign an NSRect to it using code below,
self.view.window?.setFrame(NSMakeRect(0, 0, 430, 310), display: true, animate: true)
Is there a way to change window frame size without change its location?

You can use the origin of the original frame and make a new NSRect with a different size.
Something like this for instance:
func resizeFrame(newWidth: CGFloat, newHeight: CGFloat) {
if let originalFrame = view.window?.frame {
let newSize = CGSize(width: newWidth, height: newHeight)
view.window?.setFrame(NSRect(origin: originalFrame.origin, size: newSize), display: true, animate: true)
}
}
Used like this:
resizeFrame(newWidth: 430, newHeight: 310)
Hope that helps.

As Gerd K said, the y coordinate needs to be adjusted. To save others time, here is that calculation fashioned after pbodsk's example:
func resizeFrame(newWidth: CGFloat, newHeight: CGFloat) {
if let originalFrame = view.window?.frame {
let newY = originalFrame.origin.y + originalFrame.size.height - newHeight
view.window?.setFrame(NSRect(x: originalFrame.origin.x, y: newY, width: newWidth, height: newHeight), display: true, animate: true)
}
}
Usage:
resizeFrame(newWidth: 430, newHeight: 310)

Related

Customising UItababbar swift

Can anyone point me how could I achieve such design in the UITabbar. I have tried adding the back-ground Image, but that does not look like the design. Here the curve is extended beyond the frame of UITabbar, not sure how to add this views on top of active tabbar.
Creating a custom TabBar from UITabBarController can be solved the problem. Instead of adding a direct image to the Tabbar, use an on the fly image using UIGraphicsBeginImageContext for selectedTabBackgroundImage.
Create the image.
Clip the top part round in the image
Here is the example of the code.
import UIKit
class CustomTabBarViewController: UITabBarController {
var topClipSize: CGFloat = 24.5 //Adjust based on the number of tabbar
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let singleTabWidth: CGFloat = self.tabBar.frame.size.width / CGFloat((self.tabBar.items?.count)!)
let singleTabSize = CGSize(width:singleTabWidth , height: self.tabBar.frame.size.height)
// Create the backgound image
let selectedTabBackgroundImage: UIImage = self.imageWithColor(color: .blue, size: singleTabSize)
// Clip the top
self.tabBar.selectionIndicatorImage = selectedTabBackgroundImage.roundTopImage(topClipSize: topClipSize)
}
func imageWithColor(color: UIColor, size: CGSize) -> UIImage {
if size.height > 55 {
topClipSize = 30.0 // iPhone 8 tabbar height is 53 and iPnone X is 83 - We need more space on top.
}
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height + topClipSize)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
extension UIImage {
func roundTopImage(topClipSize: CGFloat) -> UIImage {
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size)
let rectBounds: CGRect = CGRect(x: rect.origin.x, y: rect.origin.y + (topClipSize * 2), width: rect.size.width, height: rect.size.height - (topClipSize * 2))
let ovalBounds: CGRect = CGRect(x: rect.origin.x - topClipSize, y: rect.origin.y, width: rect.size.width + (topClipSize * 2), height: rect.size.height)
UIGraphicsBeginImageContextWithOptions(self.size, false, 1)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
rectPath.addClip()
self.draw(in: rect)
return UIGraphicsGetImageFromCurrentImageContext()!
}
}
Here is the output:
It's not really possible to elegantly change the native UITabBar's appearance to that extent. Your options are to create a custom container view controller that acts like a UITabBarController, or just hide the default tab bar and implement your own view in that space.
Even though it's less elegant because you'd be just throwing a view on top of the default tab bar, I actually like that method because you retain the benefits of the native UITabBarController (calling self.tabBarController? from its view controllers, it already adjusts layout margins, etc).
To do this, in your subclass of UITabBarController hide the tabBar:
self.tabBar.isHidden = true
self.tabBar.alpha = 0
Then after implementing your custom view however you want, just set the frame of your custom view to self.tabBar.frame in viewDidLayoutSubviews.
For changing viewControllers, call this when the user taps one of your custom tabs:
self.selectedIndex = newIndex

Text Direction of NSTextField

I want a NSTextField with text direction as
how could I achieve this? thanks in advance for any help.
Choose orientation to VerticalĀ and then set text.
The input must be look like that.
I missed the NSTextFieldpoints so did it with NSTextView thanks to comment of #Willeke.
Cocoa framework has some points to rotate UI components.
For your NSTextField
myTextField.rotate(byDegrees: 90)
set your width & height carefully.
I rotate the NSTextField
nameTF.rotate(byDegrees: 270)
calculate the new height and width of NSTextField
let newHeight = nameTF.bestHeight(for: nameTF.stringValue, width: nameTF.frame.width)
let newWidth = nameTF.bestWidth(for: nameTF.stringValue, height: nameTF.frame.height)
set the new frame of NSTextField
nameTF.frame = CGRect(x: nameTF.frame.origin.x, y: self.view.frame.height - newWidth - 30, width: newHeight, height: newWidth)
used the extension of NSTextField
extension NSTextField {
func bestHeight(for text: String, width: CGFloat) -> CGFloat {
stringValue = text
let height = cell!.cellSize(forBounds: NSRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude)).height
return height
}
func bestWidth(for text: String, height: CGFloat) -> CGFloat {
stringValue = text
let width = cell!.cellSize(forBounds: NSRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: height)).width
return width
}
}

How can I show the rest of a character that has gone slightly outside of UITextView?

I have a UITextView which changes size depending on the text the user inputs (the purple box), which is inside another UIView (the red box).
But when using a handwritten style font like this, the end character sometimes gets cut off at the edge:
I have tried used text1.clipsToBounds = false but that didn't show the edge of the character. Is there a way to show the full character without changing the width of the text view?
Also here is the code I am using to set up the text view:
let text1 = UITextView()
text1.text = ""
text1.font = UIFont(name: "Gotcha", size: 27)
text1.frame = CGRect(x: 10, y: 5, width: 70, height: 50)
text1.isScrollEnabled = false
text1.delegate = self
text1.textAlignment = .center
text1.isEditable = false
text1.isSelectable = false
holdingView.addSubview(text1)
The frame then gets updated with this function, and whenever the text is changed:
func adjustTextViewSize(_ textView: UITextView) {
let maxWidth = 300
let newSize = textView.sizeThatFits(CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude))
textView.frame = CGRect(x: (textView.frame.minX), y: (textView.frame.minY), width: newSize.width, height: newSize.height)
}
Thanks!
Update:
I solved this by adding an extra 30px to newSize.width for any font that is handwritten:
if fontFile?.isHandwritten == true {
currentView.widthConstraint?.constant = newSize.width + 30
currentTextHoldingView.widthConstraint?.constant = newSize.width + 30
}
call this function for get height according to string length
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin,
attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.height)
}
}

NSWindow scaled drawing in contentView

I'm creating a bare-bones window programmatically and I just need to draw some lines in its content view. The size of the area the lines fall in (worldBounds) needs to be scaled down and I assume I need the contentView bounds origin to be the same as worldBounds origin so it can draw everything. The approach is to set the frame to 1/8 the size, then use setBoundsOrigin and setBoundsSize on the contentView, which I understand scales the coordinate system.
The problem is no drawing appears. The window appears with the correct size. I have a function drawTestLines set up just to test it. If I remove the calls to setBounds, everything draws fine, but of course then the bounds don't match worldBounds. Any help is much appreciated!
var worldBounds = NSRect()
let scale: CGFloat = 0.125
func drawMap() {
boundLineStore(lineStore, &worldBounds)
worldBounds.origin.x -= 8
worldBounds.origin.y -= 8
worldBounds.size.width += 16
worldBounds.size.height += 16
if !draw {
return
}
let scaled = NSRect(x: 300.0, // to set the window size/position
y: 80,
width: worldBounds.size.width*scale, // 575
height: worldBounds.size.height*scale) // 355
window = NSWindow(contentRect: scaled,
styleMask: .titled,
backing: .buffered,
defer: false)
window.display()
window.orderFront(nil)
window.contentView!.setBoundsSize(worldBounds.size) // (4593, 2833)
window.contentView!.setBoundsOrigin(worldBounds.origin) // (-776, -4872)
// Draw map lines
window.contentView!.lockFocus()
drawTestLines(in: window.contentView!)
window.contentView!.unlockFocus()
}
// for testing
func drawTestLines(in view: NSView) {
let bottom = view.bounds.minY
let top = view.bounds.maxY
let left = view.bounds.minX
let right = view.bounds.maxX
NSColor.black.setStroke()
for i in Int(left)..<Int(right) { // draw vertical lines across the view just to see if it works!
if i%64 == 0 {
NSBezierPath.strokeLine(from: NSPoint(x: CGFloat(i), y: bottom), to: NSPoint(x: CGFloat(i), y: top))
}
}
NSBezierPath.strokeLine(from: NSPoint(x: left, y: bottom), to: NSPoint(x: right, y: top))
NSBezierPath.strokeLine(from: NSPoint.zero, to: NSPoint(x: 50.0, y: 50.0))
}
Experiment: Create a new Xcode project. Change applicationDidFinishLaunching:
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let contentBounds = window.contentView?.bounds {
let scale: CGFloat = 0.5
let worldBounds = CGRect(x: 0.0, y: 0.0, width: contentBounds.size.width * scale, height: contentBounds.size.height * scale)
window.contentView!.bounds = worldBounds
}
}
Change the class of the content view of the window in IB to MyView. Add a new class MyView, subclass of NSView. Change draw to:
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let bottom = self.bounds.minY
let top = self.bounds.maxY
let left = self.bounds.minX
let right = self.bounds.maxX
NSColor.black.setStroke()
for i in Int(left)..<Int(right) { // draw vertical lines across the view just to see if it works!
if i%64 == 0 {
NSBezierPath.strokeLine(from: NSPoint(x: CGFloat(i), y: bottom), to: NSPoint(x: CGFloat(i), y: top))
}
}
NSBezierPath.strokeLine(from: NSPoint(x: left, y: bottom), to: NSPoint(x: right, y: top))
NSBezierPath.strokeLine(from: NSPoint.zero, to: NSPoint(x: 50.0, y: 50.0))
}
See what happens when you change scale or the origin of worldBounds.

What is the difference between NSRectFill and NSBezierPath(rect).fill

I know I can fill a rect using NSRectFill(bounds). However I wanted to preserve transparency for PDF output and I discovered that I can do that only with NSBezierPath(rect: bounds).fill()
What is the difference (behind the scenes) of those two?
func drawBackground() {
CGContextSaveGState(currentContext)
if (NSGraphicsContext.currentContextDrawingToScreen()) {
NSColor(patternImage: checkerboardImage).set()
NSRectFillUsingOperation(bounds, NSCompositingOperation.CompositeSourceOver)
}
NSColor.clearColor().setFill()
//NSRectFill(bounds) //option 1
NSBezierPath(rect: bounds).fill() // option 2
CGContextRestoreGState(currentContext)
}
extension NSImage {
static func checkerboardImageWithSize(size : CGFloat) -> NSImage {
let fullRect = NSRect(x: 0, y: 0, width: size, height: size)
let halfSize : CGFloat = size * 0.5;
let upperSquareRect = NSRect(x: 0, y: 0, width: halfSize, height: halfSize);
let bottomSquareRect = NSRect(x: halfSize, y: halfSize, width:halfSize, height: halfSize);
let image = NSImage(size: NSSize(width: size, height: size))
image.lockFocus()
NSColor.whiteColor()
NSRectFill(fullRect)
NSColor(deviceWhite: 0.0, alpha:0.1).set()
NSRectFill(upperSquareRect)
NSRectFill(bottomSquareRect)
image.unlockFocus()
return image
}
}
I'm mostly an iOS programmer and not very fluent these days over on the AppKit side of things, but my guess is that you're getting the wrong NSCompositingOperation. I see from the docs that NSRectFill uses NSCompositeCopy. Perhaps it would work better if you used NSRectFillUsingOperation, where you get to specify the compositing operation.