I take a picture using the iPhone's camera. The taken resolution is 3024.0 x 4032. I then have to apply a watermark to this image. After a bunch of trial and error, the method I decided to use was taking a snapshot of a watermark UIView, and drawing that over the image, like so:
// Create the watermarked photo.
let result: UIImage=UIGraphicsImageRenderer(size: image.size).image(actions: { _ in
image.draw(in: .init(origin: .zero, size: image.size))
let watermark: Watermark = .init(
size: image.size,
scaleFactor: image.size.smallest / self.frame.size.smallest
)
watermark.drawHierarchy(in: .init(origin: .zero, size: image.size), afterScreenUpdates: true)
})
Then with the final image — because the client wanted it to have a filename as well when viewed from within the Photos app and exported from it, and also with much trial and error — I save it to a file in a temporary directory. I then save it to the user's Photo library using that file. The difference as compared to saving the image directly vs saving it from the file is that when saved from the file the filename is used as the filename within the Photos app; and in the other case it's just a default photo name generated by Apple.
The problem is that in the image saving code I'm getting the following error:
[Metal] 9072 by 12096 iosurface is too large for GPU
And when I view the saved photo it's basically just a completely black image. This problem only started when I changed the AVCaptureSession preset to .photo. Before then there was no errors.
Now, the worst problem is that the app just completely crashes on drawing of the watermark view in the first place. When using .photo the resolution is significantly higher, so the image size is larger, so the watermark size has to be commensurately larger as well. iOS appears to be okay with the size of the watermark UIView. However, when I try to draw it over the image the app crashes with this message from Xcode:
So there's that problem. But I figured that could be resolved by taking a more manual approach to the drawing of the watermark then using a UIView snapshot. So it's not the most pressing problem. What is, is that even after the drawing code is commented out, I still get the iosurface is too large error.
Here's the code that saves the image to the file and then to the Photos library:
extension UIImage {
/// Save us with the given name to the user's photo album.
/// - Parameters:
/// - filename: The filename to be used for the saved photo. Behavior is undefined if the filename contain characters other than what is represented by this regular expression [A-Za-z0-9-_]. A decimal point for the file extension is permitted.
/// - location: A GPS location to save with the photo.
fileprivate func save(_ filename: String, _ location: Optional<Coordinates>) throws {
// Create a path to a temporary directory. Adding filenames to the Photos app form of images is accomplished by first creating an image file on the file system, saving the photo using the URL to that file, and then deleting that file on the file system.
// A documented way of adding filenames to photos saved to Photos was never found.
// Furthermore, we save everything to a `tmp` directory as if we just tried deleting individual photos after they were saved, and the deletion failed, it would be a little more tricky setting up logic to ensure that the undeleted files are eventually
// cleaned up. But by using a `tmp` directory, we can save all temporary photos to it, and delete the entire directory following each taken picture.
guard
let tmpUrl: URL=try {
guard let documentsDirUrl=NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
throw GeneralError("Failed to create URL to documents directory.")
}
let url: Optional<URL> = .init(string: documentsDirUrl + "/tmp/")
return url
}()
else {
throw GeneralError("Failed to create URL to temporary directory.")
}
// A path to the image file.
let filePath: String=try {
// Reduce the likelihood of photos taken in quick succession from overwriting each other.
let collisionResistantPath: String="\(tmpUrl.path(percentEncoded: false))\(UUID())/"
// Make sure all directories required by the path exist before trying to write to it.
try FileManager.default.createDirectory(atPath: collisionResistantPath, withIntermediateDirectories: true, attributes: nil)
// Done.
return collisionResistantPath + filename
}()
// Create `CFURL` analogue of file path.
guard let cfPath: CFURL=CFURLCreateWithFileSystemPath(nil, filePath as CFString, CFURLPathStyle.cfurlposixPathStyle, false) else {
throw GeneralError("Failed to create `CFURL` analogue of file path.")
}
// Create image destination object.
//
// You can change your exif type here.
// This is a note from original author. Not quite exactly sure what they mean by it. Link in method documentation can be used to refer back to the original context.
guard let destination=CGImageDestinationCreateWithURL(cfPath, UTType.jpeg.identifier as CFString, 1, nil) else {
throw GeneralError("Failed to create `CGImageDestination` from file url.")
}
// Metadata properties.
let properties: CFDictionary={
// Place your metadata here.
// Keep in mind that metadata follows a standard. You can not use custom property names here.
let tiffProperties: Dictionary<String, Any>=[:]
return [
kCGImagePropertyExifDictionary as String: tiffProperties
] as CFDictionary
}()
// Create image file.
guard let cgImage=self.cgImage else {
throw GeneralError("Failed to retrieve `CGImage` analogue of `UIImage`.")
}
CGImageDestinationAddImage(destination, cgImage, properties)
CGImageDestinationFinalize(destination)
// Save to the photo library.
PHPhotoLibrary.shared().performChanges({
guard let creationRequest: PHAssetChangeRequest = .creationRequestForAssetFromImage(atFileURL: URL(fileURLWithPath: filePath)) else {
return
}
// Add metadata to the photo.
creationRequest.creationDate = .init()
if let location=location {
creationRequest.location = .init(latitude: location.latitude, longitude: location.longitude)
}
}, completionHandler: { _, _ in
try? FileManager.default.removeItem(atPath: tmpUrl.absoluteString)
})
}
}
If anyone can provide some insight as to what's causing the iosurface is too large error and what can be done to resolve it, that'd be awesome.
Post
Replies
Boosts
Views
Activity
I am working on implementing in-app purchases for my iOS app, specifically auto-renewable subscriptions. I've been trying to understand the differences between transaction.webOrderLineItemID and transaction.id and how they can be used in managing subscriptions.
Both of these properties seem to provide unique identifiers for transactions, but I am unclear about the specific benefits of using webOrderLineItemID over id. Can you please provide clarification on the following points?
What are the exact use cases where using webOrderLineItemID is more beneficial than id when managing auto-renewable subscriptions?
Can different transactions have the same value for webOrderLineItemID? If not, how does it provide additional granularity or context compared to id?
I appreciate any insights you can provide on this topic, as I want to ensure that I am using the appropriate identifiers for managing auto-renewable subscriptions in my app. Thank you!
I just had an app transferred to my developer account, it says "Ready for Sale", but is not giving me any option to publish it.
I've reached out to support, did the whole "give us steps to reproduce this problem" dance, but no response as of yet; and I sent the requested details on the 4th.
Is anyone else experiencing this problem and no of a resolution?
Web docs state this:
The number of consumable products purchased.
Property documentation states this:
/// Quantity of `productID` purchased in the transaction.
/// - Note: Always 1 for non-consumables and auto-renewable suscriptions.
My question is whether or not this property applies to non-renewing subscriptions. As consumables are understood to have a technical meaning in most contexts regarding store kit (not including nonrenewing subscriptions). But then it doesn't clarify that in the code docs, only states that it is always 1 for only non-consumables and auto-renewable subscriptions; which makes the documentation implicitly contradictory.
According to code docs, does it mean it's not always 1 for non-renewing subscriptions? But aren't non-renewing subscriptions not considered consumable — which would contradict the web documentation.
So what do I mean by "truly" custom animations. I mean an animation that you don't use any frameworks for, that you effect by manipulating the values of the view yourself by such small amounts and with such frequency that the change is fluid.
For example, I have a drag and drop gesture recognizer for stackviews; although I've made some alterations not reflected in that file. The problem with it is that it does not account for scrollable lists in that you want the list to scroll upwards if you want to move an item your dragging to a position not visible within the bounds of the scrollview. So when you drag it close to the top, you want it to scroll upwards; and same for the bottom.
I tried using UIView.animate(...) to change the offset to zero at an acceptable rate — and then cancel it if it moves sufficiently far from the top or reaches beginning — but on doing so the arranged subview that was being dragged immediately disappears. I'll be honest I don't remember whether it ends up getting placed wherever you happened to be holding it over at that time, or it goes back to where it started; but it doesn't work.
So I decided to do this manually. Change the content offset at a rate that effects fluid scrolling. So the increment/decrement is tentatively 0.34. And I change it at a rate starting from once every 5 milliseconds down to as fast as possible — depending on how close the touch is to the top of the scrollview frame. The change is affected using a loop in a background thread that sleeps in each iteration, and how long it sleeps for depends on how close the user is to the top/bottom. The closer, the faster, so the less it sleeps for.
I'm wondering if executing changes to the UI thread with such frequency is an established bad idea, if that's exactly how Apple does it... Basically whether it's safe or not — will there be a high probability of crashes resulting from engaging that functionality? It absolutely does not crash during testing because of this, but it wouldn't be the first time that things worked for me during testing but failed after being shipped.
This is taken from a completely fresh multiplatform SwiftUI project. I want to be able to make a custom view focusable, so I tried testing with a Text view but it's not working. Isn't that the whole point of .focusable() to declare an arbitrary view focusable? If that's the case, then why isn't it working in the below code:
import SwiftUI
struct ContentView: View {
@FocusState private var focusedItem: Optional<FocusedItem>
var body: some View {
VStack {
Text("Test")
.focusable(true)
.focused($focusedItem, equals: .two)
Button("Toggle Focus") {
self.focusedItem=(self.focusedItem == nil) ? .two : nil
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// Always prints `nil`.
print(self.focusedItem)
}
}
}
}
enum FocusedItem {
case one, two
func other() -> FocusedItem {
switch self {
case .one: return .two
case .two: return .one
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Update
It appears that the content offset is only set as a consequence of calling UITextField.resignFirstResponder() when the main scrollview is scrolled by a certain amount. In other words, there's a threshold. If the content offset is underneath the threshold calling resignFirstResponder() won't result in the content offset being set, and if above it, then it will be set to it. In other words it appears that UIKit has a problem with the content offset being higher than X, and if it is at the time the keyboard is dismissed, then it is set to X.
Original
I have a UIScrollView that is normally the full width and height of the screen. It is constrained to the top anchor of the keyboard layout guide. So when the keyboard pops up, this scrollview's frame shrinks by the height of the keyboard.
Inside this scrollview is another scrollview. It contains a stackview that hosts the UITextFields. The fields are scrollable, and the main scrollview is only scrollable when the keyboard is displayed.
The problem occurs when the keyboard is being dismissed. I'm trying to access the content offset of the main scrollview at the time keyboard begins to hide. I do this using keyboard will hide/show notifications. However, at this point in time the content offset has been set to something different than it was at the time the user dismissed the keyboard.
I narrowed down the problem to the UITextField.resignFirstResponder() method. I overrode the contentOffset property of the main scrollview and found using breakpoints that when resignFirstResponder() is called on the focused textfield, the content offset of the scrollview is changed by UIKit.
What I want is for UIKit to stop changing the content offset when UITextField.resignFirstResponder() is called. This way I can get the content offset at the time the user dismissed the keyboard from within the keyboard will hide callback.
I'll create a minimal reproducible example if really required, but I thought I'd take a shot and see if this issue rings a bell for anyone that already knows a solution to it.
I have UIButtons with images set that have white elements to them. The problem is that when UIButton.adjustsImageWhenHighlighted is true, then the white areas of the image are turned grey when pressing the button. I don't want UIButton to have this effect on my image, so I set it to false and it solves the problem. But now it is deprecated. What is the alternative solution to setting it to false to achieve the same behavior without setting a .highlighted image?
The parameters of the completion handler are (data: Optional<Data>, urlResponse: Optional<Data>, error: Optional<Error>). Here is an exhaustive switch statement of the parameter combinations:
switch (data, ulrResponse, error) {
case (.some, .some, .some):
case (.some, .some, .none):
case (.some, .none, .some):
case (.some, .none, .none):
case (.none, .some, .some):
case (.none, .none, .some):
case (.none, .some, .none):
case (.none, .none, .none):
}
Which combinations of values may be provided and which will not?
For example, I think it's fair to say that both (.some, .none, .some) and (.some, .none, .none) are invalid because a response would have to be provided in order to receive data to pass to the closure, and the documentation states:
If a response from the server is received, regardless of whether the request completes successfully or fails, the response parameter contains that information.