How can I make my macOS-App print a long text on multiple pages? I need to print the content of an entity-attribute with a very long text over more than 1 page.
Printing func so far, as I found it and combined from many examples:
private func onPrint() {
let printInfo = NSPrintInfo()
printInfo.topMargin = 20.0
printInfo.bottomMargin = 20.0
printInfo.leftMargin = 20.0
printInfo.rightMargin = 20.0
printInfo.orientation = .portrait
printInfo.isHorizontallyCentered = false
printInfo.isVerticallyCentered = false
printInfo.scalingFactor = 1.0
let paperSize = printInfo.paperSize
let view = CorrespondenceLetterPrintView(contact: corresp.toContact!, subject: corresp.subject!, letterText: corresp.body!)
let pageContentSize = NSSize(width: paperSize.width - printInfo.leftMargin - printInfo.rightMargin,
height: paperSize.height - printInfo.topMargin - printInfo.bottomMargin)
let contentRect = NSRect(origin: .zero, size: pageContentSize) // these values will vary
let viewToPrint = NSHostingView(rootView: view)
viewToPrint.frame = contentRect
let bitMap = viewToPrint.bitmapImageRepForCachingDisplay(in: contentRect)!
viewToPrint.cacheDisplay(in: contentRect, to: bitMap)
let image = NSImage(size: bitMap.size)
image.addRepresentation(bitMap)
let imageView = NSImageView(frame: contentRect)
imageView.image = image
let printOperation = NSPrintOperation(view: imageView, printInfo: printInfo)
printOperation.showsPrintPanel = true
printOperation.showsProgressPanel = true
printOperation.run()
}
My View used:
struct CorrespondenceLetterPrintView: View {
[...]
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("\(subject)").controlSize(.regular)
Image("pfeil-links-auswahl")
.frame(width: 100, height: 50, alignment: .center)
.scaledToFit()
Text("\(letterText)") //<<-- here the very long text, might be 2 or more pages
.font(.system(size: 24))
.multilineTextAlignment(.leading)
.controlSize(.regular)
}
.padding()
.foregroundColor(Color.black)
.background(Color.white)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
}.frame(alignment: .topLeading)
}
}
But only page 1 is printed and the long text is cropped.
And additionally: is it possible, to print Images without turning the view into a graphics, because than the text looks fringed.
Related
How can you assign different actions if you are creating buttons in a loop when using SwiftUI.
I was using sender tag while using UIView.
At following code, every buttons calls the same function as usual.
How can I make them call different action in case we are using loops to create buttons.
var buttonNames = ["OK", "NOPE"]
var body: some View {
let width = UIScreen.main.bounds.width / CGFloat(buttonNames.count)
ScrollView (.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(0..<buttonNames.count, id: \.self) { index in
Button(buttonNames[index]) {
buttonTouched()
}.frame(width: width)
.background(Color.gray)
}
}
.frame(width: UIScreen.main.bounds.width, height: 45, alignment: .center)
.foregroundColor(.white)
.background(Color.gray)
}
.position(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height - 45)
}
After the comment I did it like this and it makes the trick.
var buttonNames = ["OK", "NOPE"]
#State var touchedButton = 0
func buttonTouched(){
print("Button tapped! \(touchedButton)")
}
var body: some View {
let width = UIScreen.main.bounds.width / CGFloat(buttonNames.count)
ScrollView (.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(0..<buttonNames.count, id: \.self) {
index in
Button(action:{
touchedButton = index
buttonTouched()
}
){
Text(buttonNames[index])
}
.frame(width: width)
}
}
.frame(width: UIScreen.main.bounds.width, height: 45, alignment: .center)
.foregroundColor(.white)
.background(Color.gray)
} .position(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height - 45)
}
KFImage within LazyHStask flashes image while dragging the view in spite of I set loadDiskFileSynchronously() to the view.
I suspect that KFImage loads image every time its onAppear() called, and it makes image flashing.
But it seems that KFImage preventing waste reloading image inside its onAppear().
https://github.com/onevcat/Kingfisher/blob/e30efd82368276664b9ffb8a10dc29cd6a6810da/Sources/SwiftUI/KFImageRenderer.swift#L75
How can I fix this?
This is sample snippet reproducing the problem.
struct SampleView : View {
let urls: [URL?]
#GestureState private var draggingOffset: CGFloat = 0
#State private var index: Int = 0
init(urls: [URL?]) {
self.urls = urls
}
var body: some View {
LazyHStack(spacing: 0) {
ForEach(urls.indices, id: \.self) { position in
KFImage(urls[position])
.resizable()
.loadDiskFileSynchronously()
.frame(width: UIScreen.main.bounds.width, alignment: .center)
.scaledToFit()
}
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height, alignment: .leading)
.offset(x: -CGFloat(index) * UIScreen.main.bounds.width)
.offset(x: draggingOffset)
.animation(.interactiveSpring(), value: index)
.animation(.interactiveSpring(), value: draggingOffset)
.gesture(
DragGesture()
.updating($draggingOffset) { value, state, _ in
state = value.translation.width
}
.onEnded { value in
let offset = value.translation.width / UIScreen.main.bounds.width
let newIndex = (CGFloat(index) - offset).rounded()
index = min(max(Int(newIndex), 0), urls.count - 1)
}
)
}
}
flashing images(gif)
I'm trying to construct a view in SwiftUI, where the user can keep zooming in and out, and show elements across the view. But the rectangle keeps the size of the window, and scales down when zooming out instead of filling the body. The body (black) correctly fills the window.
How do you make the white rectangle fill the body when zooming out?
(Must be run in an app instead of preview)
import SwiftUI
func rgb (_ count: Int) -> [Color]{
let colors = [Color.red, Color.green, Color.blue]
var arr: [Color] = []
for i in 0..<count {
arr.append(colors[i%3])
}
return arr
}
struct ContentView: View {
#State var scale: CGFloat = 1.0
var body: some View {
let colors = rgb(20)
ZStack {
Rectangle()
.fill(Color.white)
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .center)
ForEach(colors.indices.reversed(), id: \.self) { i in
Circle()
.size(width: 100, height: 100)
.fill(colors[i])
.offset(x: 100.0*CGFloat(i), y: 100.0*CGFloat(i))
}
}
.drawingGroup()
.scaleEffect(scale)
.gesture(MagnificationGesture()
.onChanged {self.scale = $0})
.background(Color.black)
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .center)
}
}
I put this an an answer to show a screenshot. The second bit of code behaves very inconsistently. I never see 20 circles. It will zoom, but seems to then be caught in some other view. It is very strange behavior and tough to explain. While the screenshot is here, I could run it 20 times and get 20 different screenshots if I zoom and/or resize the window. I am not on Apple silicon, so your first post may be a bug in implementation on Apple silicon. Wouldn't be the first.
Functioning example for this use case, with rectangle removed from ZStack:
import SwiftUI
func rgb (_ count: Int) -> [Color]{
let colors = [Color.red, Color.green, Color.blue]
var arr: [Color] = []
for i in 0..<count {
arr.append(colors[i%3])
}
return arr
}
struct ContentView: View {
#State var scale: CGFloat = 1.0
#State var colorIndex = 0
var bgColor: Color { rgb(3)[colorIndex%3] }
var body: some View {
let colors = rgb(20)
ZStack {
ForEach(colors.indices.reversed(), id: \.self) { i in
Circle()
.size(width: 100, height: 100)
.fill(colors[i])
.offset(x: 100.0*CGFloat(i), y: 100.0*CGFloat(i))
}
}
.drawingGroup()
.scaleEffect(scale)
.background(bgColor)
.gesture(MagnificationGesture()
.onChanged {scale = $0})
.gesture(TapGesture().onEnded({colorIndex+=1}))
}
}
However it does not fix the problem of the shape not scaling to the body size.
I am trying to constrain image size between min and max. But the view expands both width and height to their max values, removing the original aspect ratio.
import SwiftUI
struct ImageConstrainSizeTest: View {
var body: some View {
Image("bike")
.resizable()
.aspectRatio(contentMode: .fit)
.border(Color.yellow, width: 5)
.frame(minWidth: 10, maxWidth: 300, minHeight: 10, maxHeight: 300, alignment: .center)
.border(Color.red, width: 5)
}
}
struct ImageConstrainSizeTest_Previews: PreviewProvider {
static var previews: some View {
ImageConstrainSizeTest()
}
}
In the screenshot below, I want the red box to shrink to yellow box.
Tried using GeometryReader, but that gives the opposite effect of expanding the yellow box to red box.
Any thoughts?
Here is a demo of possible approach - using view preferences and a bit different layout order (we give area to fit image with aspect and then by resulting image size constrain this area).
Demo prepared & tested with Xcode 11.7 / iOS 13.7
struct ViewSizeKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: CGSize = .zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}
struct ImageConstrainSizeTest: View {
#State private var size = CGSize.zero
var body: some View {
Color.clear
.frame(width: 300, height: 300)
.overlay(
Image("img")
.resizable()
.aspectRatio(contentMode: .fit)
.border(Color.yellow, width: 5)
.background(GeometryReader {
Color.clear.preference(key: ViewSizeKey.self,
value: $0.size) })
)
.onPreferenceChange(ViewSizeKey.self) {
self.size = $0
}
.frame(maxWidth: size.width, maxHeight: size.height)
.border(Color.red, width: 5)
}
}
This is a follow-up question from Content hugging priority behaviour in SwiftUI.
I have a List with async-loaded images for each row, which has its height set using a GeometryReader. Full code here:
struct CountryCell: View {
let country: Country
#State var childSize: CGSize = .init(width: 0, height: 50)
var body: some View {
HStack {
AsyncImage(url: Endpoints.flag(countryCode: country.flagCode).url, placeholder: Image("flag"))
.aspectRatio(contentMode: .fit)
.frame(width: DeviceMetrics.size.width * 0.25, height: self.childSize.height)
VStack(alignment: .leading, spacing: 5) {
Text("Country: ").bold() + Text(self.country.name)
Text("Capital: ").bold() + Text(self.country.capital)
Text("Currency: ").bold() + Text(self.country.currency)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.background(
GeometryReader { proxy -> AnyView in
DispatchQueue.main.async {
self.childSize = proxy.size
}
return AnyView(Color.clear)
})
}
}
}
Run it, the images won't replace the placeholder (maybe 1 in 10 will randomly show up), although the network requests are made. I can't figure it out, but have a hunch it's a race condition during triggering layout by the GeometryReader and the AsyncImage. If you replace:
.frame(width: DeviceMetrics.size.width * 0.25, height: self.childSize.height)
with:
.frame(width: DeviceMetrics.size.width * 0.25)
then the images will show up correctly. Similarly, if you comment out the GeometryReader, things will start to work too.
Any hints would be much appreciated.