In iOS each app lives inside of its sandbox - it means that developer does not have access to any other files and folders outside of the application.
But it is not a problem! It is fine to live inside of this sandbox - you can create all types of files and directories you want. However, you need to know where exactly it can be created.
There are lots of places (directories) where a developer can access file system (inside of the sandbox, of course). For now, we will talk only about most important ones:
NSLibraryDirectory
- used for documentation, support
and configuration files;
NSDocumentDirectory
- can be used to store user's
documents and other data that was generated by user when Working
with an app;
tmp
directory - used for some temporary data that
does not need to be persisted;
NSCachesDirectory
- should be used for storing caches;
It is important to remember the difference between all these
directories and use a correct one for saving your data. For
example, you don't want to save some important documents that user wanted
to save into
/tmp
directory, because it is periodically cleaned by iOS, and all data
inside it can be lost.
To save or read a file, a developer first needs to get a path to a correct
directory where this file will be (or already) stored. As usual,
you can do it in a plain Objective-C way, or you can use some gems
like
sugarcube
:
#
# Objective-C way:
#
# path to a file in a Documents directory:
directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true)
directory = directories[0]
file = directory.stringByAppendingPathComponent("some_file.pdf")
# path to a file in a Caches directory:
directories = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true)
directory = directories[0]
file = directory.stringByAppendingPathComponent("some_file.pdf")
# path to a file in a tmp directory:
directory = NSTemporaryDirectory()
file = directory.stringByAppendingPathComponent("some_file.pdf")
#
# sugarcube way:
#
# path to a file in a Documents directory:
"some_file.pdf".document_path
# path to a file in a Caches directory:
"some_file.pdf".cache_path
# path to a file in a tmp directory:
"some_file.pdf".temporary_path
Let's use
tutorial_app
to learn how one can work with files in iOS.
We will add a new screen with a text
field where a user can paste a URL. It will also have
a button that will start downloading
an image from a given URL and then save it into
documents folder with a file name
image.png
. To display an image screen will have
an imageView. If the app already has some files downloaded, it will
load it by default. Otherwise, it will be empty, and will wait for an
image to be downloaded.
First we need to add
afmotion
gem to the Gemfile, and then run
bundle install
to install a new gem,
and
rake pod:install
after, to install
AFNetworking
cocoapod, which is a
afmotion's
dependency.
As usual, we need to add a new cell to the
HomeScreen
and add an action to it. Then
we will create a layout for a new screen,
and the screen itself. Let's start with
HomeScreen
:
class HomeScreen < PM::Screen
title "Home"
# HomeScreen code ...
def set_table_data
# set_table_data code ...
{
title: "Files and Images",
}
# rest of set_table_data code ...
end
# HomeScreen code ...
def tableView(table, didSelectRowAtIndexPath: index)
# ...
elsif index.row == 5
open FilesScreen.new
end
end
end
Now we are going to create
FilesScreenLayout
in
/app/layouts/screens/files_screen_layout.rb
:
class FilesScreenLayout < MotionKit::Layout
def layout
add UITextField, :url
add UIButton, :get
add UIImageView, :image
end
def url_style
text "https://www.google.com/logos/doodles/2015/ukraine-independence-day-2015-5171096236064768-hp2x.jpg"
border_style UITextBorderStyleRoundedRect
constraints do
center_x.equals(:superview, :center_x)
top.equals(:superview, :top).plus(90)
left.equals(:superview, :left).plus(10)
right.equals(:superview, :right).minus(10)
end
end
def get_style
title "Get!"
title_color :blue.uicolor
constraints do
center_x.equals(:superview, :center_x)
top.equals(:url, :bottom).plus(20)
end
end
def image_style
content_mode UIViewContentModeScaleAspectFit
constraints do
center_x.equals(:superview, :center_x)
top.equals(:get, :bottom).plus(20)
width(300)
height(300)
end
end
end
Layout code should be easy to understand - just three elements, constraints,
nothing new.
class FilesScreen < PM::Screen
title "Files and Images"
def load_view
@layout = FilesScreenLayout.new
self.view = @layout.view
end
def on_load
self.view.backgroundColor = 0xffffff.uicolor
@layout.get(:get).on_tap do
download_image_from @layout.get(:url).text.to_s
end
@image_view = @layout.get(:image)
@get = @layout.get(:get)
load_image
end
def image_file_name
"image.png"
end
def load_image
data = NSData.read_from(image_file_name.document_path)
if data.is_a?(NSData)
@image_view.image = data.uiimage
end
end
def save_image(image)
image.nsdata.write_to(image_file_name.document_path)
end
def download_image_from(url)
return if url.empty?
mp url
@get.setTitle "Wait for it...", forState: UIControlStateNormal
AFMotion::Image.get(url) do |result|
@get.setTitle "Get", forState: UIControlStateNormal
mp result
if result.success? && result.object.is_a?(UIImage)
@image_view.image = result.object
save_image(result.object)
else
UIAlertController.alert self, "Error",
message: "Failed to download image",
buttons: ["OK"]
end
end
end
end
Let's check what's going on on the screen.
load_view
method is simple as usual - we just render our
layout. Then in
on_load
,
we save main elements into instance variables to access
them later. We also set an action to a
Get
button - each time it tapped, we will call a
download_image_from
method, and will pass it text from a
:url
text field.
At the end of on_load
, you can find
load_image
method. It checks whether the user already
has an image saved as a file and if true image gets rendered in
an image view. First it tried to read a file from a given path
with
NSData.read_from(image_file_name.document_path)
.
If it contains some data, it translates it into an image with
data.uiimage
code, and show it in the image view.
In iOS, all images are stored in files as
NSData
, which is a binary data.
Before saving an image we need to translate
it into NSData
first, and then we can save it to the
file system. Same story with reading - when we read a file, we receive
data first. To show it somewhere developer will need to convert it into
UIImage
.
save_image
method should be very easy to understand.
We pass a
UIImage
object (which is an image), translating it into data with
image.nsdata
, and then writing the result to a given
file.
download_image_from
may look complicated, but since
we already learned networking, you should understand what is going
on there. In the very beginning, we check passed
url
. We continue only if it is not empty. To show
user that we are in process of downloading we should show some loading
indicator, but for now, we just change the title of a button to
Wait for it...
. When
afmotion
receives a response, we change button's title
back and checking what we have received. If everything is fine,
and
result.object
is an image, we show it to the user and
save it with a
save_image
method. Otherwise, we display a simple alert that
tells the user that some error occurred.
Everything seems to work well, and it is a time for a traditional git commit!
git add .
git commit -m "added files screen"
Usually writing or reading a file in iOS requires three simple steps:
sugarcube
first and second steps can be combined. For
example
"some_file.pdf".document_path
;