Can't Get iOS Print Renderer to Draw Properly - ios

OK. There seems to be a dearth of examples on this, and I am fairly stumped.
I'm trying to make a custom print page renderer; the type that completely customizes the output, not one that uses an existing view.
The really weird thing, is that I was able to do this in ObjC a couple of years ago, and I can't seem to do the same thing in Swift.
I should mention that I am using the prerelease (Beta 5) of Xcode, and Swift 4 (Which has almost no difference at all from Swift 3, in my project).
The project is here.
It's a completely open-source project, so nothing's hidden; however, it's still very much under development, and is a moving target.
This is the page renderer class.
BTW: Ignore the delegate class. I was just thrashing around, trying to figure stuff up. I'm not [yet] planning on doing any delegate stuff.
In particular, my question concerns what's happening here:
override func drawContentForPage(at pageIndex: Int, in contentRect: CGRect) {
let perMeetingHeight: CGFloat = self.printableRect.size.height / CGFloat(self.actualNumberOfMeetingsPerPage)
let startingPoint = max(self.maxMeetingsPerPage * pageIndex, 0)
let endingPointPlusOne = min(self.maxMeetingsPerPage, self.actualNumberOfMeetingsPerPage)
for index in startingPoint..<endingPointPlusOne {
let top = self.printableRect.origin.y + (CGFloat(index) * perMeetingHeight)
let meetingRect = CGRect(x: self.printableRect.origin.x, y: top, width: self.printableRect.size.width, height: perMeetingHeight)
self.drawMeeting(at: index, in: meetingRect)
}
}
and here:
func drawMeeting(at meetingIndex: Int, in contentRect: CGRect) {
let myMeetingObject = self.meetings[meetingIndex]
var top: CGFloat = contentRect.origin.y
let topLabelRect = CGRect(x: contentRect.origin.x, y: 0, width: contentRect.size.width, height: self.meetingNameHeight)
top += self.meetingNameHeight
let meetingNameLabel = UILabel(frame: topLabelRect)
meetingNameLabel.backgroundColor = UIColor.clear
meetingNameLabel.font = UIFont.boldSystemFont(ofSize: 30)
meetingNameLabel.textAlignment = .center
meetingNameLabel.textColor = UIColor.black
meetingNameLabel.text = myMeetingObject.name
meetingNameLabel.draw(topLabelRect)
}
Which is all called from here:
#IBAction override func actionButtonHit(_ sender: Any) {
let sharedPrintController = UIPrintInteractionController.shared
let printInfo = UIPrintInfo(dictionary:nil)
printInfo.outputType = UIPrintInfoOutputType.general
printInfo.jobName = "print Job"
sharedPrintController.printPageRenderer = BMLT_MeetingSearch_PageRenderer(meetings: self.searchResults)
sharedPrintController.present(from: self.view.frame, in: self.view, animated: false, completionHandler: nil)
}
What's going on, is that everything on a page is being piled at the top. I am trying to print a sequential list of meetings down a page, but they are all getting drawn at the y=0 spot, like so:
This should be a list of meeting names, running down the page.
The way to get here, is to start the app, wait until it's done connecting to the server, then bang the big button. You'll get a list, and press the "Action" button at the top of the screen.
I haven't bothered to go beyond the preview, as that isn't even working. The list is the only one I have wired up right now, and I'm just at the stage of simply printing the meeting names to make sure I have the basic layout right.
Which I obviously don't.
Any ideas?

All right. I figured out what the issue was.
I was trying to do this using UIKit routines, which assume a fairly high-level drawing context. The drawText(in: CGRect) thing was my lightbulb.
I need to do everything using lower-level, context-based drawing, and leave UIKit out of it.
Here's how I implement the drawMeeting routine now (I have changed what I draw to display more relevant information). I'm still working on it, and it will get larger:
func drawMeeting(at meetingIndex: Int, in contentRect: CGRect) {
let myMeetingObject = self.meetings[meetingIndex]
var remainingRect = contentRect
if (1 < self.meetings.count) && (0 == meetingIndex % 2) {
if let drawingContext = UIGraphicsGetCurrentContext() {
drawingContext.setFillColor(UIColor.black.withAlphaComponent(0.075).cgColor)
drawingContext.fill(contentRect)
}
}
var attributes: [NSAttributedStringKey : Any] = [:]
attributes[NSAttributedStringKey.font] = UIFont.italicSystemFont(ofSize: 12)
attributes[NSAttributedStringKey.backgroundColor] = UIColor.clear
attributes[NSAttributedStringKey.foregroundColor] = UIColor.black
let descriptionString = NSAttributedString(string: myMeetingObject.description, attributes: attributes)
let descriptionSize = contentRect.size
var stringRect = descriptionString.boundingRect(with: descriptionSize, options: [NSStringDrawingOptions.usesLineFragmentOrigin,NSStringDrawingOptions.usesFontLeading], context: nil)
stringRect.origin = contentRect.origin
descriptionString.draw(at: stringRect.origin)
remainingRect.origin.y -= stringRect.size.height
}

Related

"removeFromSuperview" NEVER work on UIApplication?

How to remove subviews?
I am trying to integrate GIF by creating UIView and UIImageView programmatically.
It works fine to show GIF but when the function of hiding if is called, there is no response.
Here are the codes of both functions.
class CustomLoader: UIView {
static let instance = CustomLoader()
var viewColor: UIColor = .black
var setAlpha: CGFloat = 0.5
var gifName: String = ""
lazy var transparentView: UIView = {
let transparentView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
transparentView.backgroundColor = viewColor.withAlphaComponent(setAlpha)
transparentView.isUserInteractionEnabled = false
return transparentView
}()
lazy var gifImage: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.contentMode = .scaleAspectFit
gifImage.center = transparentView.center
gifImage.isUserInteractionEnabled = false
gifImage.loadGif(name: gifName)
return gifImage
}()
func showLoaderView() {
self.addSubview(self.transparentView)
self.transparentView.addSubview(self.gifImage)
self.transparentView.bringSubview(toFront: self.gifImage)
UIApplication.shared.keyWindow?.addSubview(transparentView)
}
func hideLoaderView() {
self.transparentView.removeFromSuperview()
}
}
A couple of thoughts:
I’d suggest you add a breakpoint or a logging statement in hideLoaderView and make sure you’re getting to that line.
You should make the init method to this class private to make sure you’re not calling hideLoaderView on some separate instance. When dealing with singletons, you want to make sure you can’t accidentally create another instance.
But I tested your code, and it works fine. Your problem probably rests with where and how you call this (and making init private, you might find where you might be using it inappropriately).
In the comments below, you said:
I simply call the function "CustomLoader().hideLoaderView()" Both are being called technically. What do you mean by "where I using it inappropriately?"
That is the root of the problem.
The CustomLoader() of CustomLoader().hideLoaderView() will create a new instance of CustomLoader with its own transparencyView, etc., which is precisely what the problem is. You’re not hiding the old view that was presented earlier, but trying to hide another one that you just created and was never displayed.
If you instead use that static, e.g. CustomLoader.instance.showLoaderView() and CustomLoader.instance.hideLoaderView(), then the problem will go away. Then you will be hiding the same view that your previously showed.
By the way, a few other unrelated observations:
If this is a singleton or shared instance, the convention would be to call that static property shared, not instance.
By the way, you aren’t using this CustomLoader as a UIView, so I’d not make it a UIView subclass. Don’t make it a subclass of anything.
You would obviously eliminate that self.addSubview(transparentView) line, too.
The bringSubview(toFront:) call is unnecessary.
You should avoid referencing UIScreen.main.bounds. You don’t know if your app might be in multitasking mode (maybe this isn’t an issue right now, but it’s the sort of unnecessary assumption that will cause problems at some later date). Just refer to the bounds of the UIWindow to which you’re adding this. You should also update this frame when you show this view, not when you create it (in case you changed orientation in the intervening time, or whatever).
By the way, using keyWindow is discouraged in iOS 13 and later, so you might eventually want to remove that, too.
When adding the gifImage (which I’d suggest renaming to gifImageView because it’s an image view, not an image), you should not reference the center of its superview. That’s the coordinate of the transparent view in its super view’s coordinate system, which could be completely different than the transparent view’s own coordinate system. In this case, it just happens to work, but it suggests a fundamental misunderstanding of view coordinate systems. Reference the bounds of the transparentView, not its center.
If you’re going to expose viewColor and setAlpha, you should pull the setting of the transparentView’s color out of the lazy initializer and into showLoaderView, at the very least. Right now, if you show the loader once, and then change the color, and try to show it again, you won’t see the new color.
The same issue applies with the gif image. So, I’d move that to the didSet observer.
Thus, pulling this all together:
class CustomLoader{
static let shared = CustomLoader()
private init() { }
var dimmingColor: UIColor = .black
var dimmingAlpha: CGFloat = 0.5
var gifName: String = "" { didSet { gifImage.loadGif(name: gifName) } }
lazy var transparentView: UIView = {
let transparentView = UIView()
transparentView.isUserInteractionEnabled = false
return transparentView
}()
lazy var gifImageView: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.contentMode = .scaleAspectFit
gifImage.isUserInteractionEnabled = false
return gifImage
}()
func showLoaderView() {
guard let window = UIApplication.shared.keyWindow else { return }
transparentView.frame = window.bounds
transparentView.backgroundColor = dimmingColor.withAlphaComponent(dimmingAlpha)
gifImageView.center = CGPoint(x: transparentView.bounds.midX, y: transparentView.bounds.midY)
transparentView.addSubview(gifImageView)
window.addSubview(transparentView)
}
func hideLoaderView() {
transparentView.removeFromSuperview()
}
}
Why you are using transparentView while you are have a CustomLoader instance view
Try to use this
class CustomLoader: UIView {
static let instance = CustomLoader()
var viewColor: UIColor = .black
var setAlpha: CGFloat = 0.5
var gifName: String = ""
init() {
super.init(frame: UIScreen.main.bounds)
backgroundColor = viewColor.withAlphaComponent(setAlpha)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var gifImage: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.backgroundColor = .red
gifImage.contentMode = .scaleAspectFit
gifImage.center = center
gifImage.isUserInteractionEnabled = false
gifImage.loadGif(name: gifName)
return gifImage
}()
func showLoaderView() {
addSubview(self.gifImage)
UIApplication.shared.keyWindow?.addSubview(self)
}
func hideLoaderView() {
removeFromSuperview()
}
}

how to copy one view to another view in Swift

I want to copy one UIView to another view without making it archive or unarchive.
Please help me if you have any solution.
I tried with by making an extension of UIView as already available an answer on Stack over flow. But its crashing when I pass the view with pattern Image Background color.
The code related to my comment below:
extension UIView
{
func copyView() -> UIView?
{
return NSKeyedUnarchiver.unarchiveObjectWithData(NSKeyedArchiver.archivedDataWithRootObject(self)) as? UIView
}
}
I've just tried this simple code in a Playground to check that the copy view works and it's not pointing the same view:
let originalView = UIView(frame: CGRectMake(0, 0, 100, 50));
originalView.backgroundColor = UIColor.redColor();
let originalLabel = UILabel(frame: originalView.frame);
originalLabel.text = "Hi";
originalLabel.backgroundColor = UIColor.whiteColor();
originalView.addSubview(originalLabel);
let copyView = originalView.copyView();
let copyLabel = copyView?.subviews[0] as! UILabel;
originalView.backgroundColor = UIColor.blackColor();
originalLabel.text = "Hola";
originalView.backgroundColor; // Returns black
originalLabel.text; // Returns "Hola"
copyView!.backgroundColor; // Returns red
copyLabel.text; // Returns "Hi"
If the extension wouldn't work, both copyView and originalView would have same backgroundColor and the same would happen to the text of the labels. So maybe there is the possibility that the problem is in other part.
Original Post
func copyView(viewforCopy: UIView) -> UIView {
viewforCopy.hidden = false //The copy not works if is hidden, just prevention
let viewCopy = viewforCopy.snapshotViewAfterScreenUpdates(true)
viewforCopy.hidden = true
return viewCopy
}
Updated for Swift 4
func copyView(viewforCopy: UIView) -> UIView {
viewforCopy.isHidden = false //The copy not works if is hidden, just prevention
let viewCopy = viewforCopy.snapshotView(afterScreenUpdates: true)
viewforCopy.isHidden = true
return viewCopy!
}

Create UIContainerView programmatically

I've just started to code my app in Swift 2 and avoiding the use of XIBs and storyboards.
However, I am unable to replicate the following feature. It's exactly what I wanted.
I've tried creating a UIView to perform the following using .backgroundColor and it works, however, I am unable to link it to my UIViewControllers. Just wondering how is it done? How do I link my UIView to my UIViewController?
Codes:
let subFrame : CGRect = CGRectMake(0,screenHeight*1/2.75,screenWidth,screenHeight)
var loginView = SignUpViewController()
let signUpView: UIView = UIView(frame: subFrame)
signUpView.backgroundColor = UIColor.redColor()
//Controls what each segment does
switch segmentView.indexOfSelectedSegment {
case 0:
self.view.addSubview(signUpView)
case 1:
self.view.addSubview(loginView)
default:
break;
}
I'm not even sure if .view.addSubview(xxx) overwrites/replaces the original subview if it is not this way. Is this the right way to do it?
Do not just start coding an app if you are not familiar with simple things of the OOP (Object-Oriented-Programming) language like Swift. This is not the way how to learn a programming language. Sure you could learn while experimenting but it is better to understand the book first before starting with more complex stuff. Read a few more pages of the Swift book from Apple. Most classes for iOS development are still Objective-C wrapped classes (reference type because the top superClass is probably NSObject; keep this in mind).
Here is the code example you wanted:
class ViewController: UIViewController {
let firstView = UIView()
let secondView = UIView()
let segmentedControlView = UISegmentedControl(items: ["firstView", "secondView"])
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor() // we need this for the playground
/* setup your view here */
/* add your sigment logic somewhere */
self.view.addSubview(self.segmentedControlView)
self.view.addSubview(self.firstView)
self.view.addSubview(self.secondView)
self.segmentedControlView.frame = CGRect(x: 0, y: 20, width: self.view.frame.width, height: 44)
self.segmentedControlView.selectedSegmentIndex = 0 // enable the first segment
self.segmentedControlView.addTarget(self, action: "segmentIndexChanged:", forControlEvents: UIControlEvents.ValueChanged)
/* add your own frame calculation here */
/* I prefer AutoLayout, but for the example static frames will be fine */
self.firstView.frame.origin = CGPoint(x: 0, y: self.segmentedControlView.frame.origin.y + self.segmentedControlView.frame.height)
self.firstView.frame.size = CGSize(width: self.view.frame.width, height: self.view.frame.height - self.segmentedControlView.frame.origin.y)
// to prevent same code, we just copy the same frame from the firstView
// both will sit in the same place
self.secondView.frame = self.firstView.frame
/* lets add some colors so we'll see our views */
self.firstView.backgroundColor = UIColor.blueColor()
self.secondView.backgroundColor = UIColor.redColor()
self.secondView.hidden = true // when intializer the secondView is not visible
}
func segmentIndexChanged(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
self.firstView.hidden = false
self.secondView.hidden = true
case 1:
self.firstView.hidden = true
self.secondView.hidden = false
default:
break;
}
}
}
If you do not understand a function, should should look up its definition in the developer docs. (Like: addSubview)

Accessibility Voice Over loses focus on Segmented SubView

I'm working on an Accessibility project where I have a segmentedController in the NavigationBar. Almost everything is working fine until the focus comes at the middle (2/3) SegmentedController. It won't speak the the accessibilityLabel..
See my code.
I'm using NSNotifications to let the 'UIAccessibilityPostNotification' know when to focus:
func chatLijst() {
let subViews = customSC.subviews
let lijstView = subViews.last as UIView
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, lijstView)
}
func berichtenLijst() {
let subViews = customSC.subviews
let messageView = subViews[1] as UIView
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, messageView)
}
func contactenLijst() {
let subViews = customSC.subviews
let contactenView = subViews.first as UIView
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, contactenView)
}
func setupSegmentedController(){
let lijst:NSString = "Lijst"
lijst.isAccessibilityElement = false
lijst.accessibilityLabel = "Lijst met gesprekken"
let bericht:NSString = "Bericht"
bericht.isAccessibilityElement = false
bericht.accessibilityLabel = "Bericht schrijven"
let contacten:NSString = "Contacten"
contacten.isAccessibilityElement = false
contacten.accessibilityLabel = "Contacten opzoeken"
let midden:CGFloat = (self.view.frame.size.width - 233) / 2
customSC.frame = CGRectMake(midden, 7, 233, 30)
customSC.insertSegmentWithTitle(lijst, atIndex: 0, animated: true)
customSC.insertSegmentWithTitle(bericht, atIndex: 1, animated: true)
customSC.insertSegmentWithTitle(contacten, atIndex: 2, animated: true)
customSC.selectedSegmentIndex = 0
customSC.tintColor = UIColor.yellowColor()
customSC.isAccessibilityElement = true
self.navigationController?.navigationBar.addSubview(customSC)
}
Fix
Strange enough I had to restructure the subViews array in the setup func and replace UIAccessibilityPostNotification object with the new segmentsView array.
func chatLijst() {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, segmentsViews[0])
}
// Restructure subviews....
segmentsViews = [customSC.subviews[2], customSC.subviews[1], customSC.subviews[0]]
I'm using NSNotifications to let the 'UIAccessibilityPostNotification' know when to focus
Don't. That's a poor way to build a custom accessible control, and more importantly it can be confusing to the user. The screen changed notification doesn't just change focus, it also plays a specific sound that indicates to the user that the contents of the screen has changed.
Instead, I would recommend that you either make the subviews that you want appear as accessibility elements be accessibility elements with their own labels and traits and then rely on the OS to focus and activate them, or that you implement the UIAccessibilityContainer protocol in your custom control and then rely on the OS to focus and activate them.

Lag using runAction with a SKLabelNode on swift

I am having a lag issue with this function that is used a lot of times in my app...
plusOne(scorelabel.position,plus: 1)
And:
func plusOne(position: CGPoint, plus : Int) {
myLabel.setScale(1)
myLabel.text = "+"+String(plus)
myLabel.position = position
myLabel.hidden = false
let action1 = SKAction.scaleTo(2, duration: 0.5)
let action2 = SKAction.fadeOutWithDuration(0.5)
let actionGroup = SKAction.group([action1,action2])
myLabel.runAction(actionGroup,completion: {
self.myLabel.hidden = true
})
}
The first time I use the plusOne function, always make my app be freezed for a little time...
I do not know if I have been doing the things well... myLabel has been declared global but it is the same... always with lag on the first execution.
You need to set the font of your label with a fix font at start.
Like that:
let yourFont = UIFont(name: "yourfontName", size: 17)
var myLabel = SKLabelNode(fontNamed: yourFont?.fontName)
Otherwise, your font gets loaded at the first usage and not on app-start.

Resources