In the following code, the UIKeyboard is bigger than the view.
Question: How to make the keyboard same size as the view?
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController, UITextFieldDelegate {
override func loadView() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .white
let label = UITextField()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
label.addTarget(self, action: #selector(myTargetFunction), for: .touchDown)
view.addSubview(label)
self.view = view
}
#objc func myTargetFunction() {
print("It works!")
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
The only "solution" I discovered so far is to set the preferredContentSize of your UIViewController to the size of the UIScreen.main of the current Xcode Playground environment:
let vc = MyViewController()
vc.preferredContentSize = UIScreen.main.bounds.size
PlaygroundPage.current.liveView = vc
Related
I display an NSWindow that contains an NSbutton. The button can be focussed with the tab key for keyboard interaction, the standard focus ring is shown around the button.
The window has a fixed aspect ratio and can be resized as a whole. When this happens, all content should resize, i.e. the button gets larger or smaller.
This works fine so far. However, the focus ring does only kind of resize properly: the general position and its width are fine, but its heigth does not match the button’s heigth anymore.
Here is how the window initially looks like:
...and here after the window is resized:
You'll probably notice that the text gets slightly disproportional as well, but to no such extent as the focus ring is misplaced.
What steps do I have to take to have the focus ring match its corresponding button at all zoom levels?
Here is my code:
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
window.contentViewController = MyViewController(size: NSSize(width: 200, height: 72))
window.minSize = CGSize(width: 200, height: 100)
window.aspectRatio = NSMakeSize(2,1)
}
}
class MyViewController: NSViewController {
public init(size: NSSize) {
super.init(nibName: nil, bundle: nil)
self.view = MyView(frame: NSRect(x: 0, y: 0, width: size.width, height: size.height))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MyView: NSView {
let initialFirstResponder: NSView
public override init(frame: NSRect) {
let textField = NSTextField(labelWithString: "Textfield")
textField.isEditable = false
textField.frame = NSRect(x: 20, y: 36, width: 100, height: 15)
let switchButton = NSButton(title: "Switch", target: nil, action: nil)
switchButton.frame = NSRect(x: 100, y: 31, width: 80, height: 20)
self.initialFirstResponder = switchButton
super.init(frame: frame)
self.bounds = frame
self.postsBoundsChangedNotifications = true
NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: self, queue: OperationQueue.main, using: {(note) in
self.setBoundsSize(NSSize(width: 200, height: 72)) // 28px for titlebar
print("Frame nun \(self.frame.width) x \(self.frame.height)")
})
self.addSubview(textField)
self.addSubview(switchButton)
self.autoresizesSubviews = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
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'm trying to print the contents of a SwiftUI view on macOS. According to the documentation, this seems like it should be possible by giving the NSPrintOperation an NSView created by an NSHostingView. The SwiftUI view's body contains several Text views and an Image view, with borders around those views. All of the Text gets printed, however the Image and the borders are not visible. Is there something else needed to make this work?
Here is a sample to demonstrate the problem. Just create a new macOS App, replace the ContentView with the code below and then enable Printing in Signing & Capabilities:
struct ContentView: View {
var body: some View {
VStack {
Button("Print", action: self.onPrint )
Divider()
Print_Preview()
}
}
private func onPrint() {
let pi = NSPrintInfo.shared
pi.topMargin = 0.0
pi.bottomMargin = 0.0
pi.leftMargin = 0.0
pi.rightMargin = 0.0
pi.orientation = .landscape
pi.isHorizontallyCentered = false
pi.isVerticallyCentered = false
pi.scalingFactor = 1.0
let rootView = Print_Preview()
let view = NSHostingView(rootView: rootView)
view.frame.size = CGSize(width: 300, height: 300)
let po = NSPrintOperation(view: view, printInfo: pi)
po.printInfo.orientation = .landscape
po.showsPrintPanel = true
po.showsProgressPanel = true
po.printPanel.options.insert(NSPrintPanel.Options.showsPaperSize)
po.printPanel.options.insert(NSPrintPanel.Options.showsOrientation)
if po.run() {
print("In Print completion")
}
}
struct Print_Preview: View {
var body: some View {
VStack(alignment: .leading) {
Text("Bordered Text Above Bordered Image")
.font(.system(size: 8))
.padding(5)
.border(Color.black, width: 2)
Image(systemName: "printer")
.resizable()
.padding(5)
.border(Color.black, width: 2)
.frame(width: 100, height: 100)
Text("Bordered Text Below Bordered Image")
.font(.system(size: 8))
.padding(5)
.border(Color.black, width: 2)
}
.padding()
.foregroundColor(Color.black)
.background(Color.white)
.frame(width: 200, height: 200)
}
}
}
Also, here are screenshots of the App and Print Panel.
Thanks to #Willeke for the comment with a pointer and to #user2120275 for asking a different question that happened to contain the trick needed to fix my problem. The solution is to create an NSImageView from the NSView returned by the NSHostingView, and then print that NSImageView instead of the original NSView.
My sample code above can be made to work by replacing these two lines:
view.frame.size = CGSize(width: 300, height: 300)
let po = NSPrintOperation(view: view, printInfo: pi)
with the following:
let contentRect = NSRect(x: 0, y: 0, width: 300, height: 300)
view.frame.size = contentRect.size
let newWindow = NSWindow(
contentRect: contentRect,
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
newWindow.contentView = view
let myNSBitMapRep = newWindow.contentView!.bitmapImageRepForCachingDisplay(in: contentRect)!
newWindow.contentView!.cacheDisplay(in: contentRect, to: myNSBitMapRep)
let myNSImage = NSImage(size: myNSBitMapRep.size)
myNSImage.addRepresentation(myNSBitMapRep)
let nsImageView = NSImageView(frame: contentRect)
nsImageView.image = myNSImage
let po = NSPrintOperation(view: nsImageView, printInfo: pi)
I'm using SwiftUI for a Mac app where the main window contains a NavigationView. This NavigationView contains a sidebar list. When an item in the sidebar is selected, it changes the view displayed in the detail view. The views presented in the detail view are different sizes which should cause the size of the window to change when they are displayed. However, when the detail view changes size the window does not change size to accommodate the new detail view.
How can I make the window size change according to the size of the NavigationView?
My example code for the app is below:
import SwiftUI
struct View200: View {
var body: some View {
Text("200").font(.title)
.frame(width: 200, height: 400)
.background(Color(.systemRed))
}
}
struct View500: View {
var body: some View {
Text("500").font(.title)
.frame(width: 500, height: 300)
.background(Color(.systemBlue))
}
}
struct ViewOther: View {
let item: Int
var body: some View {
Text("\(item)").font(.title)
.frame(width: 300, height: 200)
.background(Color(.systemGreen))
}
}
struct DetailView: View {
let item: Int
var body: some View {
switch item {
case 2:
return AnyView(View200())
case 5:
return AnyView(View500())
default:
return AnyView(ViewOther(item: item))
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(1...10, id: \.self) { index in
NavigationLink(destination: DetailView(item: index)) {
Text("Link \(index)")
}
}
}
.listStyle(SidebarListStyle())
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And here is what the example app looks like when the detail view changes size:
Here is demo of possible approach that works. I did it on one different view, because you will need to redesign your solution to adopt it.
Demo
1) The view requiring window animated resize
struct ResizingView: View {
public static let needsNewSize = Notification.Name("needsNewSize")
#State var resizing = false
var body: some View {
VStack {
Button(action: {
self.resizing.toggle()
NotificationCenter.default.post(name: Self.needsNewSize, object:
CGSize(width: self.resizing ? 800 : 400, height: self.resizing ? 350 : 200))
}, label: { Text("Resize") } )
}
.frame(minWidth: 400, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
}
}
2) Window's owner (in this case AppDelegate)
import Cocoa
import SwiftUI
import Combine
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var subscribers = Set<AnyCancellable>()
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ResizingView()
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), // just default
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
NotificationCenter.default.publisher(for: ResizingView.needsNewSize)
.sink(receiveCompletion: {_ in}) { [unowned self] notificaiton in
if let size = notificaiton.object as? CGSize {
var frame = self.window.frame
let old = self.window.contentRect(forFrameRect: frame).size
let dX = size.width - old.width
let dY = size.height - old.height
frame.origin.y -= dY // origin in flipped coordinates
frame.size.width += dX
frame.size.height += dY
self.window.setFrame(frame, display: true, animate: true)
}
}
.store(in: &subscribers)
}
...
Asperi's answer works for me, but the animation is not working on Big Sur 11.0.1, Xcode 12.2. Thankfully, the animation works if you wrap it in an NSAnimationContext:
NSAnimationContext.runAnimationGroup({ context in
context.timingFunction = CAMediaTimingFunction(name: .easeIn)
window!.animator().setFrame(frame, display: true, animate: true)
}, completionHandler: {
})
Also it should be mentioned that ResizingView and window don't have to be initialized inside AppDelegate; you can continue using SwiftUI's App struct:
#main
struct MyApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ResizingView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow?
var subscribers = Set<AnyCancellable>()
func applicationDidBecomeActive(_ notification: Notification) {
self.window = NSApp.mainWindow
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
setupResizeNotification()
}
private func setupResizeNotification() {
NotificationCenter.default.publisher(for: ResizingView.needsNewSize)
.sink(receiveCompletion: {_ in}) { [unowned self] notificaiton in
if let size = notificaiton.object as? CGSize, self.window != nil {
var frame = self.window!.frame
let old = self.window!.contentRect(forFrameRect: frame).size
let dX = size.width - old.width
let dY = size.height - old.height
frame.origin.y -= dY // origin in flipped coordinates
frame.size.width += dX
frame.size.height += dY
NSAnimationContext.runAnimationGroup({ context in
context.timingFunction = CAMediaTimingFunction(name: .easeIn)
window!.animator().setFrame(frame, display: true, animate: true)
}, completionHandler: {
})
}
}
.store(in: &subscribers)
}
}
the following will not solve your problem, but might (with some extra work), lead you to a solution.
I did not have much to investigate further, but it's possible to overwrite the setContentSize method in NSWindow (by subclassing of course). That way you can override the default behavior, which is setting the window frame without an animation.
The problem you will have to figure out is the fact that for complex views such as yours, the setContentSize method is called repeatedly, causing the animation not to work properly.
The following example works fine, but that's because we are dealing with a very simple view:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = AnimatableWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
class AnimatableWindow: NSWindow {
var lastContentSize: CGSize = .zero
override func setContentSize(_ size: NSSize) {
if lastContentSize == size { return } // prevent multiple calls with the same size
lastContentSize = size
self.animator().setFrame(NSRect(origin: self.frame.origin, size: size), display: true, animate: true)
}
}
struct ContentView: View {
#State private var flag = false
var body: some View {
VStack {
Button("Change") {
self.flag.toggle()
}
}.frame(width: self.flag ? 100 : 300 , height: 200)
}
}
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
}