Images are overwritten when modifying a PDF iOS - ios

I have an application that allows a user to draw on a PDF. The user's drawing is saved as an image which is then added to an existing PDF. The issue I encounter is when a user has already drawn 2 images. For some reason, saving the 3rd image cause the 2nd image to be overwritten by the first. Below is an example.
PDF example:
The PDF above should read First, Second, Third; however, the 2nd image was overwritten by the first.
Below is my code for embedding an image into the PDF. Note I also tried this with PDFKit and have experienced the same result:
func saveImageToPDF(path: String , drawnImage: UIImage, x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat, pageIndex: Int) {
if let pdf = CGPDFDocument(NSURL(fileURLWithPath: path)) {
let pageCount = pdf.numberOfPages
// Write to file
UIGraphicsBeginPDFContextToFile(path, CGRect.zero, nil)
for index in 1...pageCount {
let page = pdf.page(at: index)
let pageFrame = page?.getBoxRect(.mediaBox)
if (pageFrame != nil) {
UIGraphicsBeginPDFPageWithInfo(pageFrame!, nil)
let pdfContext = UIGraphicsGetCurrentContext()
// Draw existing page
pdfContext?.saveGState()
pdfContext?.scaleBy(x: 1, y: -1)
pdfContext?.translateBy(x: 0, y: -pageFrame!.size.height)
pdfContext?.drawPDFPage(page!)
pdfContext?.restoreGState()
// Draw image on top of page
if (index == (pageIndex + 1)) {
drawnImage.draw(in: CGRect(x: x, y: y, width: width, height: height))
}
}
}
UIGraphicsEndPDFContext()
}
}
Note: I only seem to encounter this issue on iOS 15. Running the same code on iOS 14 works like a charm.
Should I consider this a bug on iOS 15, or is there something I'm missing?
Thanks in advance!

After reaching out to Apple's technical support. They concluded it was an issue with iOS 15.
Here is a sample project that replicates the issue exactly: https://github.com/Manguelo/test-pdf-issue
Note the application should be run on iOS 15.0
According to the PDFKit team this bug should be addressed in iOS 15.2 beta 3.

Related

add a drop shadow on an UIImage, not UIImageView

I need to add a drop shadow on my image, not the image view. Is there anyway to do that? I know I can add shadow to imageView like -
imageView.layer.masksToBounds true
imageView.layer.shadowColor = UIColor.gray.cgColor
imageView.layer.shadowOffset = CGSizeMake(0, 1)
imageView.layer.shadowOpacity = 1
imageView.layer.shadowRadius = 1.0
but I need to add the shadow to the image, not imageView. Does anyone have any clue?
I think you can use CIFilter in Core Image. CIFilter is an image processor that produces an image by manipulating one or more input images or by generating new image data.
You can check various references here.
I think you can CIHighlightShadowAdjust
CIHighlightShadowAdjust is the properties you use to configure a highlight-shadow adjust filter.
Just for #dfd.
So, I went and had a look at Create new UIImage by adding shadow to existing UIImage. After scrolling down a bit, I started to find several Swift based solutions. Intrigued, I threw them into a Playground to see what they could do.
I settled on this solution...
import UIKit
extension UIImage {
/// Returns a new image with the specified shadow properties.
/// This will increase the size of the image to fit the shadow and the original image.
func withShadow(blur: CGFloat = 6, offset: CGSize = .zero, color: UIColor = UIColor(white: 0, alpha: 0.8)) -> UIImage {
let shadowRect = CGRect(
x: offset.width - blur,
y: offset.height - blur,
width: size.width + blur * 2,
height: size.height + blur * 2
)
UIGraphicsBeginImageContextWithOptions(
CGSize(
width: max(shadowRect.maxX, size.width) - min(shadowRect.minX, 0),
height: max(shadowRect.maxY, size.height) - min(shadowRect.minY, 0)
),
false, 0
)
let context = UIGraphicsGetCurrentContext()!
context.setShadow(
offset: offset,
blur: blur,
color: color.cgColor
)
draw(
in: CGRect(
x: max(0, -shadowRect.origin.x),
y: max(0, -shadowRect.origin.y),
width: size.width,
height: size.height
)
)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
let sourceImage = UIImage(named: "LogoSmall.png")!
let shadowed = sourceImage.withShadow(blur: 6, color: .red)
But wait, that's not a drop shadow, it's an outline!
🙄 Apparently we need to hand hold everybody now days...
Changing the parameters to ...
let shadowed = sourceImage.withShadow(blur: 6, offset: CGSize(width: 5, height: 5), color: .red)
Produces a drop shadow. I like the solution because it doesn't make assumptions and provides a suitable number of parameters to change the output as desired.
I liked the solution so much, I copied the extension into my personal library, never know when it might come in handy.
Remember, in order to produce this style of image, the original image needs to be transparent.
A little bit like...
Remember, iOS has been around a LONG time, ObjC has been around even longer. You're likely to come across many solutions which are only presented in ObjC, which means, it's still important to have the skill/ability to at least read the code. If we're lucky, other members of the community will produce suitable Swift variants, but this isn't always possible.
I'm sure I don't need to go to the extent of writing a full tutorial on how to include images in Playground, there are plenty of examples about that 😉

Drawing a circular loader

I have to create a circular loader as per below image (Front is thicker than tail)
I am able to create a circular progress bar and also provided rotation animation.
below is code for circle
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2 * circleRadius, height: 2 * circleRadius)
let circlePathBounds = circlePathLayer.bounds
circleFrame.origin.x = circlePathBounds.midX - circleFrame.midX
circleFrame.origin.y = circlePathBounds.midY - circleFrame.midY
return circleFrame
}
func circlePath() -> UIBezierPath {
return UIBezierPath(ovalIn: circleFrame())
}
Using above code I can create a circle of equal width but not like as displayed image.
Please guide me how to create a loader like above image (tail is thinner than front). Any idea or suggestion would be great.
The simplest approach might be to create the image you want to display and then rotate it, rather than trying to draw it from scratch.
I haven't tried the following tutorial but I'm including it as a sample of how this might be done:
https://bencoding.com/2015/07/27/spinning-uiimageview-using-swift/
Note that the GitHub version (linked on that page) includes a Swift 4 update.

How to load sprite sheets from web API in SpriteKit

I'm new to SpriteKit, and my question is how to load sprite sheets from web API.
Currently, I have an API returns a big PNG image, which contains all sprite sheets, and a json about individual frame information. (file and json are generated by TexturePacker) The API looks like this:
The format just likes a .atlasc folder, which contains a big image and a plist (XML) file.
I was thinking about downloading image and plist file and save it in the disk to load. However, SKTextureAtlas.init(named: String) can only load from app bundle.
In one word, I want to load a sprite animation from the web at runtime.
I have control of the API, so I can update the API to accomplish my goal.
The way I've figured out is downloading image, create a sourceTexture, like: let sourceTexture = SKTexture(image: image)
Then use the frame information in json to create individual textures with method init(rect rect: CGRect, inTexture texture: SKTexture)
Sample code is:
var textures: [SKTexture] = []
let sourceTexture = SKTexture(image: image)
for frame in spriteSheet.frames {
let rect = CGRect(
x: frame.frame.origin.x / spriteSheet.size.width,
y: 1.0 - (frame.frame.size.height / spriteSheet.size.height) - (frame.frame.origin.y / spriteSheet.size.height),
width: frame.frame.size.width / spriteSheet.size.width,
height: frame.frame.size.height / spriteSheet.size.height
)
let texture = SKTexture(rect: rect, inTexture: sourceTexture)
textures.append(texture)
}
basically same way with #Honghao Zhang 's answer, but I was little bit confused about the whole structure at first glance.
so I share my code snippet for later readers.
Happy coding :)
func getSpriteTextures() -> [SKTexture]? {
guard let spriteSheet = loadSpriteJson(name: "sprite_json_file", codable: SpriteJson.self) else { return nil }
let sourceImage = UIImage(named: "sprite_img_file.png")!
let sourceTexture = SKTexture(image: sourceImage)
var textures: [SKTexture] = []
let sourceWidth = spriteSheet.meta.size.w
let sourceHeight = spriteSheet.meta.size.h
let orderedFrameImgNames = spriteSheet.frames.keys.sorted()
for frameImgName in orderedFrameImgNames {
let frameMeta = spriteSheet.frames[frameImgName]!
let rect = CGRect(x: frameMeta.frame.x / sourceWidth,
y: 1.0
- (frameMeta.sourceSize.h / sourceHeight)
- (frameMeta.frame.y / sourceHeight),
width: frameMeta.frame.w / sourceWidth,
height: frameMeta.frame.h / sourceHeight)
let texture = SKTexture(rect: rect, in: sourceTexture)
textures.append(texture)
}
return textures
}

Update CGRectMake to CGRect in Swift 3 Automatically

Now that CGRectMake , CGPointMake, CGSizeMake, etc. has been removed in Swift 3.0, is there any way to automatically update all initializations like from CGRectMake(0,0,w,h) to CGRect(x:0,y:0,width:w,height:h). Manual process is.. quite a pain.
Not sure why Apple don't auto convert this when I convert the code to Current Swift Syntax...
The simplest solution is probably just to redefine the functions Apple took away. Example:
func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect {
return CGRect(x: x, y: y, width: width, height: height)
}
Put that in your module and all calls to CGRectMake will work again.
Short answer: don't do it. Don't let Apple boss you around. I hate CGRect(x:y:width:height:). I've filed a bug on it. I think the initializer should be CGRect(_:_:_:_:), just like CGRectMake. I've defined an extension on CGRect that supplies it, and plop that extension into every single project the instant I start.
extension CGRect {
init(_ x:CGFloat, _ y:CGFloat, _ w:CGFloat, _ h:CGFloat) {
self.init(x:x, y:y, width:w, height:h)
}
}
That way, all I have to do is change "CGRectMake" to "CGRect" everywhere, and I'm done.
Apple actually does provide this feature. All you have to do is go to:
Edit > Convert > To Latest Swift Syntax...
And then follow the onscreen prompts.
This will solve your syntax issues and you won't have to make new functions for all of the various removed functions.

Coordinate system doesn't seem to match between Playground and iOS Device/Simulator

I've been trying to wrap my head around all the goodness in Xcode 6 and iOS 8 over the last couple of days. I'm currently working with SceneKit to get a feel for what it can do.
I'm trying to build a visual grid to make placing objects in the scene a bit easier.
The Playground displays how I expect it to, but the Simulator/Device does not. I'm not sure if it's a bug, or if I'm doing something wrong.
I have the following code:
for index in -20..20 {
let i = CFloat(index)
let neg = i - 20
let pos = i + 20
var lat = [
SCNVector3Make(neg, 0, i),
SCNVector3Make(pos, 0, i)
]
var lng = [
SCNVector3Make(i, 0, neg),
SCNVector3Make(i, 0, pos)
]
var indices: CInt[] = [0, 1]
let latSource = SCNGeometrySource(vertices:&lat, count:2)
let lngSource = SCNGeometrySource(vertices:&lng, count:2)
let indexData = NSData(bytes:indices, length:sizeof(CInt) * countElements(indices))
let element = SCNGeometryElement(data:indexData, primitiveType:SCNGeometryPrimitiveType.Line, primitiveCount:2, bytesPerIndex:sizeof(CInt))
let latLine = SCNGeometry(sources:[latSource], elements:[element])
let lngLine = SCNGeometry(sources:[lngSource], elements:[element])
let latLineNode = SCNNode(geometry:latLine)
let lngLineNode = SCNNode(geometry:lngLine)
scene.rootNode.addChildNode(latLineNode)
scene.rootNode.addChildNode(lngLineNode)
}
In a Playground the second line is let i = CGFloat(index), but other than that the code is identical between the Playground and the iOS Xcode 6 project I have.
In the Playground, I get the grid I'm after. In the Simulator and on the Device, however, I get garbage. No matter how I change the SCNVector3Make calls I can't get the grid to display properly in iOS or the Simulator.
It should also be noted that what is displayed in the Simulator and on the device is identical.
I tried adding a box to the scene also. When I used an SCNBox it displays correctly - though much bigger than it should. When I use custom geometry (that works correctly in the Playground), however, the box dimensions are way off. It looks more like a wall than a cube.
I tried to include screenshots to show what I'm seeing, but apparently I need at least 10 reputation points to post images, sorry.
Thanks in advance!
UPDATE
To answer the commenter's question below (regarding how I initialize the scene):
In the project (that runs in the Simulator/Device) this is how I get it:
let scene = SCNScene()
let sceneView = SCNView(frame:UIScreen.mainScreen().bounds)
// Build up grid
sceneView.scene = scene
self.view = sceneView
In the Playground, I do this:
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
let scene = SCNScene()
sceneView.scene = scene
XCPShowView("The Scene View", sceneView)
// Build up grid
UPDATE 2
I created an OSX app, modified the generated GameViewController code to generate the same structures and everything worked as expected. I didn't update the box's color to be red, and the positioning of the cube and camera is a bit different in the OSX app.
Now that I have enough points, I will add images showing the what I'm seeing.
Also of note I tried this on both Xcode Beta 2 & 3 - the results were identical.
What I see in the Playground
What I see in the OSX app
What I see in the iOS Simulator
I removed iOS 8 from by phone and iPad, so i don't have any screen shots from those - but they look identical to the Simulator.
I'll be filing a bug report for this through Apple.
UPDATE 3
I have created repos for these projects so anyone who's interested can take a look (maybe there's something I'm not aware of that I'm doing wrong):
Playground project
OSX project
iOS project
Please let me know if you see anything I'm doing wrong or how I could be doing things better!
Thanks for your help everyone!
I had a similar issue recently playing with UIBezierPath in Playground. Upon posting the question in Apple developer forms, I believe Playground does use a different coordinate system (LLO) than the devices (ULO):
https://forums.developer.apple.com/message/39277#39277
Here is apple's docs on the two coordinate systems:
https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW10
you have does not use on first scene (frame:UIScreen.mainScreen().bounds).
In the project (that runs in the Simulator/Device) this is how I get it:
let scene = SCNScene()
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
// Build up grid
sceneView.scene = scene
self.view = sceneView
In the Playground, I do this:
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
let scene = SCNScene()
sceneView.scene = scene
XCPShowView("The Scene View", sceneView)

Resources