I have added an image (jpg) from my Photo Album to the UserResources folder (via the + menu in the upper right of the Playground app). How do I reference that file when declaring a SwiftUI Image?
I have tried using the entire path:
let path: String = { Bundle.main.paths(forResourcesOfType: "JPG", inDirectory: nil)[0] }()
It seems to give me the path to the desired file, “PICT0024.JPG”:
/var/mobile/Containers/Data/PluginKitPlugin/F01FA67E-97CA-4590-915A-C8071507C6E0/Library/Application Support/Main Module Resources Bundles/55929C8C-46C9-4691-B86B-389D1473E9A8.bundle/Contents/Resources/PICT0024.JPG
I then declare:
Image(systemName: path)
But the image is not shown. I probably have the wrong reference, but cannot figure out what I need to specify.
Oh, just this
Image(“PICT0024.JPG”)
doesn’t work either.
What is the proper way to reference that image file?
Please confine answers to SwiftUI 5 and editing on an iPad. Using XCode at the moment isn’t an option.
Thanks.
Image(systemName: path)
this constructor one is for system SF Symbols
Image(“PICT0024.JPG”)
this constructor is for images in Assets catalog
The external images you have to use like this
Image(uiImage: UIImage(contentsOfFile: path))
import SwiftUI
import PlaygroundSupport
// Add image to the project with the "+" button,
// then select either the file, or image icon,
// then navigate to desired image,
// then add it to the iPad Playground project
let imageFileNameString = "IMG_2702.jpeg"
struct ContentView: View {
var body: some View {
// the method below requires the UIImage be unwrapped
Image(uiImage: UIImage(named: imageFileNameString)!)
// -or-
// The commented-out method below does not require any unwrapping.
// Typos will fail with "something went wrong around here" error
// Image(uiImage: UIImage(imageLiteralResourceName: imageFileNameString))
.resizable()
}
}
PlaygroundPage.current.setLiveView( ContentView() )
This is what I used:
https://www.appcoda.com/swiftui-swift-playgrounds-ipad-mac/
[Edit by original poster]
Summarizing from the article (worth a read because it discusses far more than how to access an image resource), here’s how to do it:
Type Image. and using the autocomplete bar, pick the moon and mountains icon (ImageLiteral)
That displays the “Images” popup. If you have not already added an image, choose either “Insert From...“, “Photo Library”, or “Take Photo”. Once you add an image, select it and press, “Use” (upper right corner of popover).
The result looks like this:
[The image here is of buttercups, Ranunculus acris.]
Related
Hello heroes of the internet,
When we develop apps on Xcode we can preview apps on the Canvas, and it looks something like this:
Luckily, SwiftUI allows for splitting up the code in an easy manner, with "Views". Say you have a simple view and you want to preview it on a blank canvas, not on the phone with the phone's aspect ratio and notch and all that. I want to display my view in a simple blank canvas, like in the image below.
How can I do such thing?
Change to the selectable preview mode at the bottom of the canvas:
Then add this to the preview code:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.sizeThatFits) // here
}
}
Firstly, set up your preview layout in a fixed frame like below:
struct FContentView_Previews: PreviewProvider { //don't forget to change name
static var previews: some View { //don't forget to change name
FContentView()
.previewLayout(.fixed(width: 480, height: 320)) //this line of code
}
}
Then, set the preview option to Selectable like the below image.
You can also refer to my latest updated answer: fixed frame preview in canvas
In my SwiftUI app I have a view for people to edit a book's metadata, such as its title and author. In this view I have an image view that shows the book's cover image.
struct ImageView: View {
#Binding var book: Book
var body: some View {
// coverFile is the URL to the image file.
let image = NSImage(byReferencing:
book.metadata.coverFile)
Image(nsImage: image)
.aspectRatio(contentMode: .fit)
}
}
When I open a book that I've set a cover image and open the metadata editor, the image view displays nothing. The metadata editor has a button to choose a cover image. When I click the button and choose an image file from the file importer, the image appears in the image view. If I close the book, reopen it, and open the metadata editor, the image appears in the image view. But if I restart the app, open the book, and open the metadata editor, the image view displays nothing.
I have tried the various NSImage initializers. I tried building the image from a Data object and from NSBitmapImageRep.
extension Book {
func coverImage() -> NSImage {
do {
let data = try Data(contentsOf: metadata.coverFile)
return NSImage(data: data) ?? NSImage()
} catch {
Swift.print("Error reading the image data. Error: \(error)")
}
return NSImage()
}
}
struct ImageView: View {
#Binding var book: Book
var body: some View {
let image = book.coverImage()
Image(nsImage: image)
.aspectRatio(contentMode: .fit)
}
}
But when I set a breakpoint in the image view and check the image variable, it's either nil or invalid. When I check the coverFile variable, it displays the proper URL, including the .png extension.
In an AppKit version of this app, I have the same code to create the NSImage, and the image displays in the image view.
According to Apple's documentation, the NSImage byReferencing function does not attempt to retrieve the data from the specified URL or create any image representations from that data until an app attempts to draw the image or request information about it. How do I get the SwiftUI view to trigger retrieving the image data so it displays in the image view?
UPDATE
I created a computed property for the image in the ImageView struct.
var image: NSImage {
do {
let data = try Data(contentsOf: book.metadata.coverFile)
return NSImage(data: data) ?? NSImage()
} catch {
Swift.print("Error creating the image. Error: \(error)")
}
return NSImage()
}
When I run this code, the try Data call throws an exception, the catch block runs, and the following error message is printed in Xcode's Console:
Error creating the image. Error: Error Domain=NSCocoaErrorDomain
Code=257 "The file “ImageFile” couldn’t be opened because you
don’t have permission to view it."
Choosing a file from the file importer causes the data to be created properly. I have read/write permission for the folder where the image file is.
From what I understand the problem boils down to lazily initializing the NSImage.
Have you tried using init(contentsOfFile:) or init(contentsOf:)? It does not initialize it lazily therefore should not cause this problem.
The image does not display because of a file permissions problem. If I turn on the App Sandbox and give the app Read access to the Pictures folder, the cover image appears when I open the metadata editor.
I'm trying several ways to implement image dragging (from my app to other apps) in macOS but none of them is working.
The image is a Data() object, which was taken from the clipboard, not an URL.
My code:
.onDrag {
return NSItemProvider(object: NSImage(data: self.item.value) ?? NSImage())
}
It says
Argument type 'NSImage' does not conform to expected type
'NSItemProviderWriting'
I tried with text and it's working. But can't find a way to drag an image.
The following works as Drag&Drop from testing SwiftUI app to TextEdit. Testing image image is stored in Assets.xcassets
Image("image")
.onDrag {
NSItemProvider(item: NSImage(named: "image")?.tiffRepresentation as NSSecureCoding?,
typeIdentifier: kUTTypeTIFF as String)
}
In iOS, a toolbar can be added to any view. In macOS however, it seems only possible to add a toolbar to a window.
I'm working on an app with a split view controller with a toolbar but the toolbar's items only have a meaning with respect to the right view controller's context.
E.g. let's say I have a text editor of some sort, where the left pane shows all documents (like in the Notes app) and the right pane shows the actual text which can be edited. The formatting buttons only affect the text in the right pane. Thus, it seems very intuitive to place the toolbar within that right pane instead of stretching it over the full width of the window.
Is there some way to achieve this?
(Or is there a good UX reason why this would be a bad practice?)
I've noticed how Apple solved this problem in terms of UX in their Notes app: They still use a full-width toolbar but align the button items that are only related to the right pane with the leading edge of that pane.
So in case, there is no way to place a toolbar in a view controller, how can I align the toolbar items with the leading edge of the right view controller as seen in the screenshot above?
Edit:
According to TimTwoToes' answer and the posts linked by Willeke in the comments, it seems to be possible to use Auto Layout for constraining a toolbar item with the split view's child view. This solution would work if there was a fixed toolbar layout. However, Apple encourages (for a good reason) to let users customize your app's toolbar.
Thus, I cannot add constraints to a fixed item in the toolbar. Instead, a viable solution seems to be to use a leading flexible space and adjust its size accordingly.
Initial Notes
It turns out this is tricky because there are many things that need to be considered:
Auto Layout doesn't seem to work properly with toolbar items. (I've read a few posts mentioning that Apple has classified this as a bug.)
Normally, the user can customize your app's toolbar (add and remove items). We should not deprive the user of that option.
Thus, simply constraining a particular toolbar item with the split view or a layout guide is not an option (because the item might be at a different position than expected or not there at all).
After hours of "hacking", I've finally found a reliable way to achieve the desired behavior that doesn't use any internal / undocumented methods. Here's how it looks:
How To
Instead of a standard NSToolbarFlexibleSpaceItem create an NSToolbarItem with a custom view. This will serve as your flexible, resizing space. You can do that in code or in Interface Builder:
Create outlets/properties for your toolbar and your flexible space (inside the respective NSWindowController):
#IBOutlet weak var toolbar: NSToolbar!
#IBOutlet weak var tabSpace: NSToolbarItem!
Create a method inside the same window controller that adjusts the space width:
private func adjustTabSpaceWidth() {
for item in toolbar.items {
if item == tabSpace {
guard
let origin = item.view?.frame.origin,
let originInWindowCoordinates = item.view?.convert(origin, to: nil),
let leftPane = splitViewController?.splitViewItems.first?.viewController.view
else {
return
}
let leftPaneWidth = leftPane.frame.size.width
let tabWidth = max(leftPaneWidth - originInWindowCoordinates.x, MainWindowController.minTabSpaceWidth)
item.set(width: tabWidth)
}
}
}
Define the set(width:) method in an extension on NSToolbarItem as follows:
private extension NSToolbarItem {
func set(width: CGFloat) {
minSize = .init(width: width, height: minSize.height)
maxSize = .init(width: width, height: maxSize.height)
}
}
Make your window controller conform to NSSplitViewDelegate and assign it to your split view's delegate property.1 Implement the following NSSplitViewDelegate protocol method in your window controller:
override func splitViewDidResizeSubviews(_ notification: Notification) {
adjustTabSpaceWidth()
}
This will yield the desired resizing behavior. (The user will still be able to remove the space completely or reposition it, but he can always add it back to the front.)
1 Note:
If you're using an NSSplitViewController, the system automatically assigns that controller to its split view's delegate property and you cannot change that. As a consequence, you need to subclass NSSplitViewController, override its splitViewDidResizeSubviews() method and notify the window controller from there. Your can achieve that with the following code:
protocol SplitViewControllerDelegate: class {
func splitViewControllerDidResize(_ splitViewController: SplitViewController)
}
class SplitViewController: NSSplitViewController {
weak var delegate: SplitViewControllerDelegate?
override func splitViewDidResizeSubviews(_ notification: Notification) {
delegate?.splitViewControllerDidResize(self)
}
}
Don't forget to assign your window controller as the split view controller's delegate:
override func windowDidLoad() {
super.windowDidLoad()
splitViewController?.delegate = self
}
and to implement the respective delegate method:
extension MainWindowController: SplitViewControllerDelegate {
func splitViewControllerDidResize(_ splitViewController: SplitViewController) {
adjustTabSpaceWidth()
}
}
There is no native way to achieve a "local" toolbar. You would have to create the control yourself, but I believe it would be simpel to make.
Aligning the toolbar items using autolayout is described here. Align with custom toolbar item described by Mischa.
The macOS way is to use the Toolbar solution and make them context sensitive. In this instance the text attribute buttons would enable when the right pane has the focus and disable when it looses the focus.
I'm building a typical Xcode 6 iOS app.
My goal is:
A screen that has an sub-area that can be swiped to change the content.
For example, the home screen has a logo image, a middle area that I want to be swipeable, and a bottom button.
When the user swipes (or taps) the middle area, the area shows the next (or previous) information, which is a typical UIImage and UILabel caption.
The rest of the screen stays the same, i.e. there is no navigation change.
The code is here. It use the recommendations from the StackOverflow post here.
My question: how can I implement the code below better, while still using an XIB?
My current implementation does work, and uses this approach...
A typical Swift Demo.swift file that is a UIViewController that has:
the page index, min, and max
outlets for the PageControl, UIImageView, and UILabel
actions for the page control change, and the image swipe or tap
A typical Demo.xib file that has:
a typical UIViewController for the entire screen
a UIImageView and UILabel for the changeable image and caption text
a PageControl to indicate what tutorial page the user is viewing
I am seeking better ways to accomplish this; I've read many of Xcode tutorials and so far none seem definitive for Xcode 6, XIBs, and Swift.
Here are some implementations that I've researched that seem promising...
Is there a way to implement a subview area in the XIB?
For example, can Xocde show the XIB with a rectangular area that is intended for the changeable content?
Is there an idiomatic way to write the code for changeable content?
For example, by using a ScrollView, perhaps that contains a UIPageViewController?
Is there a way to make a PageControl XIB object large enough to cover the entire UIImageView and UILabel, so I can skip making the UIImageView respond to gestures.
In my Xcode, the PageControl seems to have an uneditable height that is always 37.
The bounty will be for expert advice.
To make a UIPageViewController swipe-able you should implement the UIPageViewControllerDataSource protocol and provide a view controller for the pageViewController(pageViewController:viewControllerBeforeViewController) -> UIViewController? and the ...viewControllerAfterViewController) methods.
Provide a custom view controller for each page that presents an image and label and takes them as properties so you can provide them from the PageViewController.
My trick it to create a method that instantiates a new view controller in these methods:
// MARK:- UIPageViewControllerDataSource
extension MyPageViewController: UIPageViewControllerDataSource {
func viewControllerWithIndex(var index: Int) -> UIViewController! {
let viewController = storyboard?.instantiateViewControllerWithIdentifier("MyViewController") as! MyViewController // This VC has to be in the storyboard, otherwise just use MyVC()
// Adjust the index to be cyclical, not required
if let count = data?.endIndex {
if count == 1 && index != 0 { return nil }
if index < 0 { index += count }
index %= count
}
viewController.view.tag = index
viewController.record = data?[index]
return viewController
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let index = viewController.view?.tag ?? 0
return viewControllerWithIndex(index + 1)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let index = viewController.view?.tag ?? 0
return viewControllerWithIndex(index - 1)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return countAndSetupPageControl()
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return viewController?.view.tag ?? 0
}
}
Now for the "sub-area" you will need to implement a ChildViewController. If you're using storyboards you can just drag a Container View and put PageViewController in the embedded view controller, otherwise you need to add the PageViewController.view as a subview and set the frame to the middle.
You can find more info in the apple documentation but basically you MUST call these methods:
addChildViewController(pageViewController)
view.addSubView(pageViewController.view)
pageViewController.view.frame = ... // This is your "sub-area"
pageViewController.didMoveToParentViewController(self)
If you add a height constraint to PageControl you can set it's height to whatever you want.
I don't see a problem with your current implementation. Changing it to use a PageViewController would be quite more work.
If I were you I would add an animation in pageUpdate function so the image would fade in or slide in...
It would only make sense to use a PageViewController if you want to be able to scroll to the next page (as in content moving in the same time your finger is moving onscreen). And you can use a PageViewController or a CollectionView with paging enabled.