I'm working on a swiftUI app where I have to display multiple PDF files in one screen.
I've created a PDFView:
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
let pdfDocument = PDFDocument(url: self.url)
pdfView.document = pdfDocument
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
// Update the view.
}
}
struct PDFKitView: View {
var url: URL
var body: some View {
PDFKitRepresentedView(url)
}
}
The PDF is created here:
if let url = attachment.path {
PDFKitView(url: url)
.frame(width: UIScreen.screenWidth - 40, height: UIScreen.screenHeight - 40, alignment: .center)
.padding()
}
The problem I'm having is that, whenever the first document is multipage, in order to see the other documents the user has first to pinch to zoom out completely and the other PDF's are shown in sequence.
I've tried to add this values, but that just makes the content of the PDF to disappear
I was wondering if setting the frame on the PDFKitView directly could be causing the issue, but no.
Anyone has any suggestions on how to make this work? I assume that if I could make the pdf to show already with a min zoom it would display the view correctly.
I finally found the answer on this post How to detect where NaN is passing to CoreGraphics API on Mac OS X 10.9
Related
I'm trying to build a widget that has a gauge like in the image attached. It does not seem like there are any APIs to render the gauge (or any other view for that matter) on an arc, depending on which corner is used.
Is there any such support, or are such widgets only available to Apple? E.g. can one tell which corner the widget is being rendered in, so that the correct transformations be computed?
Thank you!
You can get close to an Apple style corner widget, but there are currently some limitations. As far as I know you have to use the .widgetLabel modifier which restricts you to an "Image, Text, Gauge, ProgressView, or a container with multiple subviews".
The styling for the Gauge and ProgressView seem to be predefined as well - for example styling the gauge with .gaugeStyle(LinearCapacityGaugeStyle()) doesn't change the appearance.
var body: some View {
switch widgetFamily {
case .accessoryCorner: // WatchOS only
Text("50%") // Watch out for clipping
.font(.system(size: 20))
.foregroundColor(.blue)
.widgetLabel {
ProgressView(value: 0.5)
.tint(.blue)
}
}
}
or
var body: some View {
switch widgetFamily {
case .accessoryCorner: // WatchOS only
Text("50%") // Watch out for clipping
.font(.system(size: 20))
.foregroundColor(.blue)
.widgetLabel {
Gauge(value: 50.0, in: 0...100) {
Text("Not shown")
} currentValueLabel: {
Text("Not shown")
} minimumValueLabel: {
Text("0") // Watch out for clipping
} maximumValueLabel: {
Text("100") // Watch out for clipping
}
.tint(.blue)
.gaugeStyle(LinearCapacityGaugeStyle()) // Doesn't do anything
}
Gives you:
You can rotate the text manually to try and make it line up with corner, but as you say then there doesn't seem to be a way to identify which corner the widget is in so you don't know which way to rotate it...
How do I automatically get the bounds of the current map view? I've been looking everywhere. The Google Maps API has it: http://www.w3schools.com/googleapi/ref_getbounds.asp
It would be nice to do it for React Native
If you want it then you can have one extension for the MKMapView as below.
extension MKMapView {
func getBounds() -> (southWestPoint: CLLocationCoordinate2D, northEastPoint: CLLocationCoordinate2D) {
let visibleMapRect = self.visibleMapRect
let neMapPoint = MKMapPointMake(MKMapRectGetMaxX(visibleMapRect), visibleMapRect.origin.y)
let swMapPoint = MKMapPointMake(visibleMapRect.origin.x, MKMapRectGetMaxY(visibleMapRect))
return (southWestPoint: MKCoordinateForMapPoint(swMapPoint), northEastPoint:MKCoordinateForMapPoint(neMapPoint))
}
}
Whenever you want the bound just call the getBounds method.
I'm trying to make an Apple Watch app (extension) which shows a map in a way similar to Apple's Maps app. I want to be able to show a portion of the map, and then be able to use the digital crown to zoom and gestures to scroll.
I think this should be possible with Watch OS2, but I haven't figured out which APIs to use.
Is this possible on the platform at present?
Update: I don't think I can use WKInterfaceMap since I want to generate my own map, not use an existing one.
Inside Watch OS2 API only WKInterfacePicker has access of Digit Crown
So We can't use Digit Crown For WKInterfaceMap Directly but we can use it by using WKInterfacePicker
Put WKInterfaceMap and WKInterfacePicker inside Group
Set WKInterfacePicker size width fixed = 2 height fixed = 2
(We can not set size to zero)
NOTE : Don't hide WKInterfacePicker, Sometime, It resign focus because of hidden .
Add below code for Picker Controller
code
class InterfaceController: WKInterfaceController {
#IBOutlet var mapView : WKInterfaceMap!
#IBOutlet var tempPicker:WKInterfacePicker!
var zoomArray:[Double] = [1.0,0.98,0.96,0.94,0.92,0.90,0.88,0.86,0.84,0.82,0.80,0.78,0.76,0.74,0.72,0.70,0.68,0.66,0.64,0.62,0.60,0.58,0.56,0.54,0.52,0.50,0.48,0.46,0.44,0.42,0.40,0.38,0.36,0.34,0.32,0.30,0.28,0.26,0.24,0.22,0.20,0.18,0.16,0.14,0.12,0.10,0.09,0.08,0.07,0.06,0.05,0.04,0.03,0.02,0.01,0.009,0.008,0.007,0.006,0.005,0.004,0.003,0.002,0.001]
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let pickerItems: [WKPickerItem] = zoomArray.map {
let pickerItem = WKPickerItem()
pickerItem.caption = String($0)
pickerItem.title = String($0)
return pickerItem
}
self.tempPicker.setItems(pickerItems)
self.tempPicker.setSelectedItemIndex(25)
let span = MKCoordinateSpanMake(zoomArray[25], zoomArray[25])
let lc = watchDelegate.locationModel.myLocation ?? CLLocationCoordinate2D.init(latitude: 0.0, longitude: 0.0)//watchDelegate.locationModel.myLocation is current user Location
let region = MKCoordinateRegionMake(lc, span)
self.mapView.setRegion(region)
}
#IBAction func pickerChanged(value: Int) {
let lc = watchDelegate.locationModel.myLocation ?? CLLocationCoordinate2D.init(latitude: 0.0, longitude: 0.0)
let span = MKCoordinateSpanMake(zoomArray[value], zoomArray[value])
let region = MKCoordinateRegionMake(lc,
span)
self.mapView.setRegion(region)
self.tempPicker.focus()
}
}
Connect #IBAction and IBOutlet to WKInterfacePicker and WKInterfaceMap
I have done a View in CollectionView with CustomLayout. In iOS6 it worked great but iOS7 it throws an exception like this.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'layout attributes for supplementary item at index path ( {length = 2, path = 0 - 0}) changed from CustomSupplementaryAttributes: 0xd1123a0 index path: (NSIndexPath: 0xd112580 {length = 2, path = 0 - 0}); element kind: (identifier); frame = (0 0; 1135.66 45); zIndex = -1; to CustomSupplementaryAttributes: 0xd583c80 index path: (NSIndexPath: 0xd583c70 {length = 2, path = 0 - 0}); element kind: (identifier); frame = (0 0; 1135.66 45); zIndex = -1; without invalidating the layout'
iOS 10
At iOS 10, a new feature is introduced, it is Cell Prefetching. It will let dynamic position of SupplementaryView crash. In order to run in the old behavior, it needs to disable prefetchingEnabled. It's true by default at iOS 10.
// Obj-C
// This function is available in iOS 10. Disable it for dynamic position of `SupplementaryView `.
if ([self.collectionView respondsToSelector:#selector(setPrefetchingEnabled:)]) {
self.collectionView.prefetchingEnabled = false;
}
// Swift
if #available(iOS 10, *) {
// Thanks #maksa
collectionView.prefetchingEnabled = false
// Swift 3 style
colView.isPrefetchingEnabled = false
}
I hate this problem. I spend 2 days for this problem.
A reference about Cell Pre-fetch #iOS 10.
iOS 9 and before ...
#Away Lin is right.. I solve the same problem by implementing that delegate method.
My Custom UICollectionViewLayout will modify the attributes in layoutAttributesForElementsInRect. The section position is dynamic, not static. So, I obtain warnings about the layout attributes for supplementary item at index path ... changed from ... to .... Before the changes, invalideLayout related methods should be called.
And, after implementing this delegate method to return true, the method invalidateLayoutWithContext: will be called when scrolling the UICollectionViewLayout. By default, it returns false.
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
From Apple Docs
Return Value
true if the collection view requires a layout update or false if the
layout does not need to change.
Discussion The default implementation of this method returns false.
Subclasses can override it and return an appropriate value based on
whether changes in the bounds of the collection view require changes
to the layout of cells and supplementary views.
If the bounds of the collection view change and this method returns
true, the collection view invalidates the layout by calling the
invalidateLayoutWithContext: method.
Availability Available in iOS 6.0 and later.
And more ...
A nice example project on GitHub, for custom UICollectionViewLayout.
You need to invalidate the existing layout before updating, see the end of the error message:
without invalidating the layout'
[collectionViewLayout invalidateLayout];
Apple Documentation for UICollectionViewLayout
I had the same exception: in iOS 7, you need now to override the inherited isEqual: in your UICollectionViewLayoutAttributes subclass as stated in Apple documentation here.
I solved my problem by override the method at the subclase of UICollectionViewFlowLayout:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound
return YES
I'm not entirely certain how or why, but this appears to be fixed in iOS 12, supporting both supplementary view resizing and prefetching. The trick for me was to make sure things are happening in the correct order.
Here is a working implementation of a stretchable header view. Notice the implementation of the header resizing happening in layoutAttributesForElements(in rect: CGRect):
class StretchyHeaderLayout: UICollectionViewFlowLayout {
var cache = [UICollectionViewLayoutAttributes]()
override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = collectionView else { return }
let sections = [Int](0..<collectionView.numberOfSections)
for section in sections {
let items = [Int](0..<collectionView.numberOfItems(inSection: section))
for item in items {
let indexPath = IndexPath(item: item, section: section)
if let attribute = layoutAttributesForItem(at: indexPath) {
cache.append(attribute)
}
}
}
if let header = layoutAttributesForSupplementaryView(ofKind: StretchyCollectionHeaderKind, at: IndexPath(item: 0, section: 0)) {
cache.append(header)
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let visibleAttributes = cache.filter { rect.contains($0.frame) || rect.intersects($0.frame) }
guard let collectionView = collectionView else { return visibleAttributes }
// Find the header and stretch it while scrolling.
guard let header = visibleAttributes.filter({ $0.representedElementKind == StretchyCollectionHeaderKind }).first else { return visibleAttributes }
header.frame.origin.y = collectionView.contentOffset.y
header.frame.size.height = headerHeight.home - collectionView.contentOffset.y
header.frame.size.width = collectionView.frame.size.width
return visibleAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as! UICollectionViewLayoutAttributes
guard collectionView != nil else { return attributes }
attributes.frame.origin.y = headerHeight.home + attributes.frame.origin.y
return attributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: StretchyCollectionHeaderKind, with: indexPath)
}
override var collectionViewContentSize: CGSize {
get {
guard let collectionView = collectionView else { return .zero }
let numberOfSections = collectionView.numberOfSections
let lastSection = numberOfSections - 1
let numberOfItems = collectionView.numberOfItems(inSection: lastSection)
let lastItem = numberOfItems - 1
guard let lastCell = layoutAttributesForItem(at: IndexPath(item: lastItem, section: lastSection)) else { return .zero }
return CGSize(width: collectionView.frame.width, height: lastCell.frame.maxY + sectionInset.bottom)
}
}
}
P.S.: I'm aware the cache doesn't actually serve any purpose at this point :)
I had this problem too, because I had code that depended on the content size of the collection view. My code was accessing the content size via the collectionView!.contentSize instead of collectionViewContentSize.
The former uses the collectionView property of UICollectionViewLayout, while the latter uses the custom-implemented layout property. In my code, the first time the layout was asked for attributes, contentSize had not been set yet.
Select the CollectionView and Goto Attribute Inspector. Uncheck The Prefetching Enabled CheckBox. This is Fixed my Issue. Screenshot
I wish to ask is there anyone face the problem when you guys activate showCamera and open photo Gallery function of titanium in android. It will crash the app and restart app automatically. I have searched a lot of forum especially JIRA appcelator and titanium forum but most of the cases are unresolved. I have tried a lot of method in the forum but still the same. I wish to know is this a bug of titanium?
The best code to choose from camera or gallery
//Create a dialog with options
var dialog = Titanium.UI.createOptionDialog({
//title of dialog
title: 'Choose an image source...',
//options
options: ['Camera','Photo Gallery', 'Cancel'],
//index of cancel button
cancel:2
});
//add event listener
dialog.addEventListener('click', function(e) {
//if first option was selected
if(e.index == 0)
{
//then we are getting image from camera
Titanium.Media.showCamera({
//we got something
success:function(event)
{
//getting media
var image = event.media;
//checking if it is photo
if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO)
{
//we may create image view with contents from image variable
//or simply save path to image
Ti.App.Properties.setString("image", image.nativePath);
}
},
cancel:function()
{
//do somehting if user cancels operation
},
error:function(error)
{
//error happend, create alert
var a = Titanium.UI.createAlertDialog({title:'Camera'});
//set message
if (error.code == Titanium.Media.NO_CAMERA)
{
a.setMessage('Device does not have camera');
}
else
{
a.setMessage('Unexpected error: ' + error.code);
}
// show alert
a.show();
},
allowImageEditing:true,
saveToPhotoGallery:true
});
}
else if(e.index == 1)
{
//obtain an image from the gallery
Titanium.Media.openPhotoGallery({
success:function(event)
{
//getting media
var image = event.media;
// set image view
//checking if it is photo
if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO)
{
//we may create image view with contents from image variable
//or simply save path to image
Ti.App.Properties.setString("image", image.nativePath);
}
},
cancel:function()
{
//user cancelled the action fron within
//the photo gallery
}
});
}
else
{
//cancel was tapped
//user opted not to choose a photo
}
});
//show dialog
dialog.show();
Thanks
I'm using this code for opening the camera and it works correctly:
Titanium.Media.showCamera({
success:function(event)
{
//getting media
var image = event.media;
im=image;
//checking if it is photo
if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO)
{
imgpath= image.nativePath;
$.userphoto.image=imgpath;//$.userphoto is an imageview
}
},
cancel:function()
{
//do somehting
},
error:function(error)
{
//error happened,
var a = Titanium.UI.createAlertDialog({title:'Camera'});
//set message
if (error.code == Titanium.Media.NO_CAMERA)
{
alert('No Cam');
}
else
{
alert('error');
}
},
mediaTypes:[Ti.Media.MEDIA_TYPE_VIDEO,Ti.Media.MEDIA_TYPE_PHOTO]
});
}
and this is the code i use for opening the photo gallery (also works fine):
Titanium.Media.openPhotoGallery({
success:function(event)
{
//check if photo
if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO)
{
im=resize(event.media); //just a function to resize the photo
imgpath= event.media.nativePath;
$.userphoto.image=imgpath;
}
},
cancel:function()
{
//user cancelled
}
});
By the way now i'm using Titanium SDK 3.4.1 GA, but it also used to work on 3.1.3GA