Learn the basics of converting addresses into GPS coordinates and vice versa through the use of Geocoding.
The post Video Tutorial: MapKit and Core Location Part 3: Geocoding appeared first on Ray Wenderlich.
Learn the basics of converting addresses into GPS coordinates and vice versa through the use of Geocoding.
The post Video Tutorial: MapKit and Core Location Part 3: Geocoding appeared first on Ray Wenderlich.
Recycling is one of those things that is good for the planet, and it’s an common sense way to make sure we don’t find ourselves buried in our own rubbish or without sufficient resources in the future.
A few Android engineers thought about the benefits of recycling and realized that an OS can also run more efficiently if it recycles. The result of this inspiration was millions of eco-Warriors and recycling enthusiasts rejoicing when the RecyclerView
widget was introduced into Android Lollipop — or something like that. :]
There was even more celebration when Google announced a support library to make this clean, green, recycling machine backwards compatible all the way to Android Eclair (2.2), which was released back in 2010!
In this tutorial, you’re going to experience the power of RecyclerView in action and learn:
You’re also going to blast off into outer space with the sample app Galacticon. You’ll use it to build out a feed of daily astronomy photos from a public NASA API.
Prerequisites: You should have a working knowledge of developing for Android before working through this tutorial. If you need a refresher, take a look at some of our introductory tutorials!
Download the starter project and open it up in Android Studio. There isn’t much to it yet, nor is the almighty RecyclerView anywhere to be seen.
Click the Run app button at the top and you’ll see something that resembles outer space in all the wrong ways:
It’s empty, but that’s ok. You wouldn’t learn much if all the work was done for you! Before you can add that amazing astrophotography from NASA, you’ll need to do some set up work.
You’ll use the Astronomy Picture of the Day API, one of the most popular web services provided by NASA. To ensure it doesn’t fall victim to unsolicited traffic, the service requires you to have an API key to use it in an application.
Fortunately, getting a key is as simple as putting your name and email address into api.nasa.gov and copying the API key that appears on the screen or in the email you receive a few moments later:
Once you’ve acquired your API key, copy it and open the strings.xml file in your project. Paste your API key into the api_key
string resource, replacing INSERT API KEY HERE
:
You’re about to blast off into outer space to explore the vastness of RecyclerViews, but no competent commander heads into the unknown without preparation. You have questions, and you need answers before you go any further. Consider this section as your mission brief.
A RecyclerView can be thought of as a combination of a ListView and a GridView. However, there are extra features that separate your code into maintainable components even as they enforce memory-efficient design patterns.
But how could it be better than the tried and tested ListView and GridView you’re used to? Could it be some kind of alien technology? The answers, as always, are in the details.
Imagine you’re creating a ListView where the custom items you want to show are quite complicated. You take time to lovingly create a row layout for these items, and then use that layout inside your adapter.
Inside your getView()
method, you inflate your new item layout. You then reference every view within by using the unique ids you provided in your XML to customize and add some view logic. Once finished, you pass that view to the ListView, ready to be drawn on the screen. All is well…or is it?
The truth is that ListViews and GridViews only do half the job of achieving true memory efficiency. They recycle the item layout, but don’t keep references to the layout children, forcing you to call findViewById()
for every child of your item layout every time you call getView()
.
All this calling around can become very processor-intensive, especially for complicated layouts. Furthermore, the situation can cause your ListView scrolling to become jerky or non-responsive as it frantically tries to grab references to the views you need.
Android engineers initially provided a solution to this problem on the Android Developers with smooth scrolling, via the power of the View Holder
pattern.
When you use this pattern, you create a class that becomes an in-memory reference to all the views needed to fill your layout. The benefit is you set the references once and reuse them, effectively working around the performance hit that comes with repeatedly calling findViewById()
.
The problem is that it’s an optional pattern for a ListView or GridView. If you’re unaware of this detail, then you may wonder why your precious ListViews and GridViews are so slow.
The arrival of the RecyclerView changed everything. It still uses an Adapter to act as a data source; however, you have to create ViewHolders to keep references in memory.
When you need a new view, it either creates a new ViewHolder object to inflate the layout and hold those references, or it recycles one from the existing stack.
Now you know why it’s called a RecyclerView!
Another perk of using RecyclerViews is that they come with default animations that you don’t have to create or add yourself — they just work.
Thanks to the requirement for a ViewHolder, the RecyclerView knows exactly which animation to apply to which item. Best of all, it just does it as required. You can even create your own animations and apply them as needed.
The last and most interesting component of a RecyclerView is its LayoutManager. This object positions the RecyclerView’s items and tells it when to recycle items that have transitioned off-screen.
Layout Managers come in three default flavors:
You can also create your own LayoutManagers
to use with a RecyclerView
if you want an extra bit of customization.
Hopefully that answers all your questions, commander. Now, onto the mission!
To create the RecyclerView, you’ll break the work into four parts:
Step one should be familiar. Open up the activity_main.xml layout file, and add the following as a child of the LinearLayout:
<android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical"/> |
Here you’re setting up the layout and telling the RecyclerView to match its parent.
Note: You’re using the v7 support library for backwards compatibility with older devices. The sample project already adds the RecyclerView Support Library as a dependency in your app’s build.gradle file. If you want more information on how to do it yourself, check out the Android developer website.
Open MainActivity.java and declare the following member variables at the top of the class:
private RecyclerView mRecyclerView; private LinearLayoutManager mLinearLayoutManager; |
In onCreate()
, add the following lines after setContentView
:
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); mLinearLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(mLinearLayoutManager); |
Phase one of ignition is complete! You’ve declared and allocated memory for two parts of the puzzle that RecyclerViews need to work: The RecyclerView and its Layout Manager.
Phase two of ignition involves creating a custom layout for the item you want your RecyclerView to use. It works exactly the same as it does when you create a custom layout for a ListView or Gridview.
Head over to your layout folder and create a new layout with the name recyclerview_item_row
and set the root element as a LinearLayout
. In your new layout, add the following XML elements as children of your LinearLayout:
<ImageView android:id="@+id/item_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="8dp" android:adjustViewBounds="true" android:layout_weight="3"/> <TextView android:id="@+id/item_date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_gravity="top|start" android:layout_weight="1" android:text="Some date"/> <TextView android:id="@+id/item_description" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center|start" android:layout_weight="1" android:ellipsize="end" android:maxLines="5"/> |
No rocket science here: You declared a few views as children of your layout, and can now use them in your adapter.
Right-click on the com.raywenderlich.galacticon folder, select New \ Java Class, and name it RecyclerAdapter. At the top of the file below the package
declaration, import the support library’s version of RecyclerView:
import android.support.v7.widget.RecyclerView; |
Make the class extend RecyclerView.Adapter so it looks like the following:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.PhotoHolder> { } |
Android Studio will detect that you’re extending a class that has required methods and will underline your class declaration with a red squiggle.
To resolve this, click on the line of code to insert your cursor, then press alt + return (or option + return) to bring up a context menu. Select Implement Methods:
Confirm you want to implement the suggested methods by clicking OK:
These methods are the driving force behind your RecyclerView adapter. Note how there is still a compiler error for the moment– this is because your adapter and the required methods are actually defined using your ViewHolder class, PhotoHolder
, which doesn’t exist just yet. You’ll get to define your ViewHolder and see what each required method does shortly, so just hang tight, Commander!
As with every adapter, you need to provide the corresponding view a means of populating items and deciding how many items there should be.
Item clicks were previously managed by a ListView’s or GridView’s onItemClickListener
. A RecyclerView doesn’t provide methods like this because it has one focus: ensuring the items inside are positioned properly and managed efficiently.
The job of listening for actions is now the responsibility of the RecyclerView item and its children. This may seem like more overhead, but in return, you get fine-grained control over how your item’s children can act.
At the top of your RecyclerAdapter class, add a variable to hold your photos:
private ArrayList<Photo> mPhotos; |
Next, add a constructor to set it up:
public RecyclerAdapter(ArrayList<Photo> photos) { mPhotos = photos; } |
Nice job, Commander! Your adapter now knows where to look for data. Soon you’ll have an ArrayList of photos filled with the finest astrophotography!
Next, you’ll populate the stubbed methods that were added by Android Studio.
The first method, getItemCount()
, is pretty simple and should be familiar from your work with ListViews or GridViews.
The adapter will work out how many items to display. In this case, you want the adapter to show every photo you’ve downloaded from NASA’s API. To do that, add the following line between the method braces:
return mPhotos.size(); |
Next, you’re going to exploit the ViewHolder
pattern to make an object that holds all your view references.
To create a PhotoHolder for your view references, you’ll create a static inner class for your adapter. You’ll add it here rather than in a separate class because its behavior is tightly coupled with the adapter.
It’s a static class, so regardless of how many instances you create, its values are shared amongst all of them — it’s pretty handy for holding references.
Add the following code underneath your adapter class member variables, but before any of the methods:
//1 public static class PhotoHolder extends RecyclerView.ViewHolder implements View.OnClickListener { //2 private ImageView mItemImage; private TextView mItemDate; private TextView mItemDescription; private Photo mPhoto; //3 private static final String PHOTO_KEY = "PHOTO"; //4 public PhotoHolder(View v) { super(v); mItemImage = (ImageView) v.findViewById(R.id.item_image); mItemDate = (TextView) v.findViewById(R.id.item_date); mItemDescription = (TextView) v.findViewById(R.id.item_description); v.setOnClickListener(this); } //5 @Override public void onClick(View v) { Log.d("RecyclerView", "CLICK!"); } } |
So what did you do here?
View.OnClickListener
since ViewHolders are responsible for their own event handling.You should now be able to build and run the app again, but it’ll look about the same because you haven’t told the RecyclerView how to associate the PhotoHolder with a view.
Sometimes there are no ViewHolders available. In this scenario, RecylerView will ask onCreateViewHolder()
from RecyclerAdapter to make a new one.
You’ll use the item layout — PhotoHolder — to create a view for the ViewHolder.
Replace the return null
line between the curly braces with the following:
View inflatedView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.recyclerview_item_row, parent, false); return new PhotoHolder(inflatedView); |
Here you add the suggested LayoutInflater import. Then you inflate the view from its layout and pass it in to a PhotoHolder.
And with that, you’ve made it so the object holds onto those references while it’s recycled, but there are still more pieces to put together before you can launch your rocketship.
Start a new activity by replacing the log in ViewHolder’s onClick
with this code:
Context context = itemView.getContext(); Intent showPhotoIntent = new Intent(context, PhotoActivity.class); showPhotoIntent.putExtra(PHOTO_KEY, mPhoto); context.startActivity(showPhotoIntent); |
This grabs the current context of your item view and creates an intent to show a new activity on the screen, passing the photo object you want to show. Passing the context object into the intent allows the app to know what activity it is leaving.
Next thing to do is to add this method inside PhotoHolder
:
public void bindPhoto(Photo photo) { mPhoto = photo; Picasso.with(mItemImage.getContext()).load(photo.getUrl()).into(mItemImage); mItemDate.setText(photo.getHumanDate()); mItemDescription.setText(photo.getExplanation()); } |
This binds the photo to the PhotoHolder, giving your item the data it needs to work out what it should show.
It also adds the suggested Picasso import, which is a library that makes it significantly simpler to get images from a given URL.
The last piece of the PhotoHolder assembly will tell it how to show the right photo at the right moment. It’s the RecyclerAdapter’s onBindViewHolder
, and it lets you know a new item will be available on screen and the holder needs some data.
Add the following code inside the onBindViewHolder()
method:
Photo itemPhoto = mPhotos.get(position); holder.bindPhoto(itemPhoto); |
Here you’re passing in a copy of your ViewHolder and the position where the item will show in your RecyclerView.
And that’s all you needed to do here on the assembly — just use the position where your ViewHolder will appear to grab the photo out of your list, and then pass it to your ViewHolder.
Step three of your ignition check protocol is complete!
This is the moment you’ve been waiting for, the final stage before blast off! All you need to do is hook your adapter up to your RecyclerView and make sure it retrieves photos when it’s created so you can explore space — in pictures.
Open MainActivity.java, and add this variable at the top:
private RecyclerAdapter mAdapter; |
Next, underneath the creation of your array list in onCreate()
add the following:
mAdapter = new RecyclerAdapter(mPhotosList); mRecyclerView.setAdapter(mAdapter); |
Here you’re creating the adapter, passing in the constructors it needs and setting it as the adapter for your RecyclerView.
Although the adapter is connected, there’s one more thing to do to make sure you don’t have an empty screen.
In onStart()
, underneath the call to super
, add this code:
if (mPhotosList.size() == 0) { requestPhoto(); } |
This adds a check to see if your list is empty, and if yes, it requests a photo.
Next, in receivedNewPhoto()
, update the method so it looks like the following:
@Override public void receivedNewPhoto(final Photo newPhoto) { runOnUiThread(new Runnable() { @Override public void run() { mPhotosList.add(newPhoto); mAdapter.notifyItemInserted(mPhotosList.size()); } }); } |
Here you are informing the recycler adapter that an item was added after the list of photos was updated.
Now you’re ready to commence the ignition sequence, er…I mean run the app.
Run the app, load up the emulator and before long, Galacticon should look something like this:
That’s not all. Tap on the photo, and you should be greeted with a new activity that brings that item into focus:
But that’s still not all! Try rotating your device or emulator (function + control + F11/F12) and you’ll see the image in full screen glory!
Depending on the size of the image and your device screen it may look a little distorted, but don’t worry about that.
Congratulations! You have a working RecyclerView and can take your journey amongst the stars.
If you head back to MainActivity on your device and try to scroll down, you’ll notice something is amiss — your RecyclerView isn’t retrieving any new photos.
Your RecyclerView is doing exactly as it’s told by showing the contents of mPhotosList
. The problem is that the app will only retrieve one photo when you load the app. It has no idea when or how to grab more photos.
So next, you’ll retrieve the number of the photos and the last visible photo index while scrolling. Then you’ll check to see if the last photo is visible and if there are no photos already on request. If these are both true, then your app goes away and downloads more pretty photos!
This patch will require a spacewalk, so break out your spacesuit and get ready for a zero gravity experience.
In MainActivity.java, add this method below onStart
:
private int getLastVisibleItemPosition() { return mLinearLayoutManager.findLastVisibleItemPosition(); } |
This uses your RecyclerView’s LinearLayoutManager to get the index of the last visible item on the screen.
Next, you add a method that inserts an onScrollListener
to your RecyclerView, so it can get a callback when the user scrolls:
private void setRecyclerViewScrollListener() { mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); int totalItemCount = mRecyclerView.getLayoutManager().getItemCount(); if (!mImageRequester.isLoadingData() && totalItemCount == getLastVisibleItemPosition() + 1) { requestPhoto(); } } }); } |
Now the RecyclerView has a scroll listener attached to it that is triggered by scrolling. During scrolling, the listener retrieves the count of the items in its LayoutManager and calculates the last visible photo index. Once done, it compares these numbers (incrementing the index by 1 because the index begins at 0 while the count begins at 1). If they match and there are no photos already on request, then you request a new photo.
Finally, hook everything to the RecyclerView by calling this method from onCreate
, just beneath where you set your RecyclerView Adapter:
setRecyclerViewScrollListener(); |
Hop back in the ship (build and run the app again). Scroll down and you should see quite an improvement!
Excellent work, your RecyclerView now updates to show the latest photo requested by your app. The great thing is that receivedNewPhoto()
handles most of the work because you told it to notify your adapter about new items.
That earns an intergalactic thumbs up for upcycling code!
Now that your RecyclerView is up and running, it’s time to trick out your spaceship.
Wouldn’t it be cool if your RecyclerView could change its layout? Good news: RecyclerView’s item positioning is separated into a layout manager.
Add a variable for a GridLayoutManager to the top of MainActivity.java:
private GridLayoutManager mGridLayoutManager; |
Note that this is a default LayoutManager, but it could just as easily be custom.
In onCreate()
, initialize the LayoutManager below the existing Linear Layout Manager:
mGridLayoutManager = new GridLayoutManager(this, 2); |
Just like you did with the previous LayoutManager, you pass in the context the manager will appear in, but unlike the former, it takes an integer parameter. In this case, you’re setting the number of columns the grid will have.
Add this method to MainActivity:
private void changeLayoutManager() { if (mRecyclerView.getLayoutManager().equals(mLinearLayoutManager)) { //1 mRecyclerView.setLayoutManager(mGridLayoutManager); //2 if (mPhotosList.size() == 1) { requestPhoto(); } } else { //3 mRecyclerView.setLayoutManager(mLinearLayoutManager); } } |
This code checks to see what LayoutManager your RecyclerView is using, and then:
Next, you need to make some changes to getLastVisibleItemPosition()
to help it handle the new LayoutManager. Replace its current contents with the following:
int itemCount; if (mRecyclerView.getLayoutManager().equals(mLinearLayoutManager)) { itemCount = mLinearLayoutManager.findLastVisibleItemPosition(); } else { itemCount = mGridLayoutManager.findLastVisibleItemPosition(); } return itemCount; |
Here you ask the RecyclerView to tell you what its LayoutManager is, then you ask that LayoutManager to tell you the position of the last visible item.
To use the grid layout, make use of the Options menu button that is already available in the app. Add the following code underneath onStart()
:
@Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_change_recycler_manager) { changeLayoutManager(); return true; } return super.onOptionsItemSelected(item); } |
This checks the ID of the item tapped in the menu, then works out what to do about it. In this case, there should only be one ID that will match up, effectively telling the app to go away and rearrange the RecyclerView’s LayoutManager.
And just like that, you’re ready to go! Load up the app and tap the button at the top right of the screen, and you’ll begin to see the stars shift:
Sometimes you’ll see things you just don’t like the look of, perhaps a galaxy far, far away that has fallen to the dark side or a planet that is prime for destruction. How could you go about killing it with a swipe?
Luckily, Android engineers have provided a useful class named ItemTouchHelper
that gives you easy swipe behavior. Creating and attaching this to a RecyclerView requires just a few lines of code.
In MainActivity.java, underneath setRecyclerViewScrollListener()
add the following method:
private void setRecyclerViewItemTouchListener() { //1 ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder viewHolder1) { //2 return false; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { //3 int position = viewHolder.getAdapterPosition(); mPhotosList.remove(position); mRecyclerView.getAdapter().notifyItemRemoved(position); } }; //4 ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchCallback); itemTouchHelper.attachToRecyclerView(mRecyclerView); } |
Let’s go through this step by step:
onMove
because you don’t want to perform any special behavior here.onSwiped
is called when you swipe an item in the direction specified in the ItemTouchHelper
. Here, you request the viewHolder
parameter passed for the position of the item view, then you remove that item from your list of photos. Finally, you inform the RecyclerView adapter that an item has been removed at a specific position.ItemTouchHelper
with the callback behavior you defined, and then attach it to the RecyclerView.Add the method to the activity’s onCreate()
, underneath setRecyclerViewScrollListener()
:
setRecyclerViewItemTouchListener(); |
This will attach the ItemTouchListener
to the RecyclerView using the code you just wrote.
Run the app once more and swipe across one of your items, you should see it begin to move. If you swipe the item far enough, you should see it animate and vanish. If other items are visible, then they will reorganize themselves to cover the hole. How cool is that?
Nice job! You’ve been on quite an adventure, but now it’s time to head back to Earth and think about what you’ve learned.
Above all, you’ve experienced how separation of components — a key attribute of RecyclerViews — provides so much functionality with such ease. If you want your collections to be flexible and provide some excitement, then look no further than the all-powerful RecyclerView.
The final project for this tutorial is available here.
If you want to learn more about RecyclerViews then check out the Android documentation to see what it can do. Take a look at the support library for RecyclerViews to learn how to use it on older devices. If you want to make them fit with the material design spec then check out the list component design specification.
Join us in the forums to discuss this tutorial and your findings as you work with RecylerViews!
Until next time, space traveler!
The post Android RecyclerView Tutorial appeared first on Ray Wenderlich.
Learn the basics of monitoring regions to create custom events when users enter or leave a defined space.
The post Video Tutorial: MapKit and Core Location Part 4: Region Monitoring appeared first on Ray Wenderlich.
I hope everyone had a wonderful week at WWDC!
This is a quick reminder that the 25% off discount for the upcoming Alt-U classes is ending soon. The discount applies to all three levels of classes:
I spoke to the guys at Five Pack Creative, and they said all classes will be fully up-to-date with Swift 3 and iOS 10 – w00t!
To get the discount, just add the course to your cart and enter this coupon code: RAYWENDERLICH. The discount expires in just a few days, so definitely snag it while you still can.
I hope you all have safe travels, and get some much-needed post-WWDC rest! :]
The post Alt-U 25% Off Discount Ending Soon appeared first on Ray Wenderlich.
Your challenge is to incorporate MapKit into the app to display the maps for each interesting location. See the Challenge PDF in the resources link below for full details.
View previous video: Region Monitoring
The post Video Tutorial: MapKit and Core Location Part 5: MapKit appeared first on Ray Wenderlich.
Learn how to annotate your map through the use of annotations.
The post Video Tutorial: MapKit and Core Location Part 6: Annotations appeared first on Ray Wenderlich.
There’s a lot of new goodies for developers in Xcode 8 and iOS 10 this year – Message Apps, SiriKit, Memory Debugging, oh my!
As usual, we’re already working on a new book to help get you up-to-speed: iOS 10 by Tutorials.
Our iOS by Tutorials series is extremely popular, and this is our fifth year in a row making a book in this series. But this year, we’re doing things differently:
Keep reading to find out about the plans for iOS 10 by Tutorials and how to get your copy.
After having watched over 55 WWDC 2016 videos, we can safely say there’s a lot iOS 10 material to cover in this book!
Here’s what we’re planning on including so far (note that since the book is in progress, this is subject to change):
Although you could teach yourself these items by reading Apple docs and sample code, it would take forever – and let’s face it, we’re all busy developers.
That’s why we create the iOS by Tutorials books each year. We do the hard work of figuring things out and making a nice easy-to-understand tutorial – that way you can quickly get up-to-speed and get back to making great apps.
Here’s how you can get your hands on the screencasts and books:
Thanks again to all raywenderlich.com subscribers – you are what makes this site possible. We hope you enjoy the iOS 10 book and screencasts – and stay tuned for tons of new Swift 3 video tutorials in the coming year! :]
The post iOS 10 by Tutorials: Free Access for Subscribers! appeared first on Ray Wenderlich.
Learn the process of drawing on your app by the way of custom overlays.
The post Video Tutorial: MapKit and Core Location Part 7: Overlays appeared first on Ray Wenderlich.
If you’ve never used Unity or if you are still really green, read this article: Introduction to Unity: Getting Started
Next Video: Operators
Previous Video: Variables
Create a new script and call it Mad Libs
. Add four variables:
myName
– string
favoriteFood
– string
amount
– double
likesToShare
– bool
Have OnDisable()
print out the following sentence:
myName
loves to eat amount
servings of favoriteFood
per day. Likes to share? likesToShare
Download Starter Challenge
Download Finished Challenge
Download Starter Demo
Download Finished Demo
The post Screencast: Beginning C# Part 3: Types appeared first on Ray Wenderlich.
Learn how use the new Messages framework in iOS 10 to construct and send completely custom messages by creating a simple game right inside iMessages.
The post iOS 10 Screencast: Sending Custom Messages in iMessage appeared first on Ray Wenderlich.
The Swift Algorithm Club is an open source project to implement popular algorithms and data structures in Swift.
Every month, Chris Pilcher and I will write a tutorial showing you how to make a cool algorithm or data structure from the project.
This series will be a great way to learn more about algorithms and data structures, and to level up your Swift skills along the way.
In this first tutorial, you’ll learn how to implement a Swift Tree data structure. This is one of the most common and useful data structures, and is a great way to get started!
The easiest way to understand the tree data structure is is through a picture:
The above diagram shows a tree with 5 levels. The root is level 0, and as you down the depth of the tree, the level increases by 1.
Trees can help you solve many important problems, including:
First, let’s cover some important terminology you should understand about trees.
The root of a tree refers to the 0th level’s node of a tree. You can also think of it as the entry point to your tree data structure.
A node is a block of data in the tree structure. The data that a node contains depends on the type of tree you’re making. The root is also a node.
Sometimes referred as a terminal node, a leaf is simply a node with no children. For example, if the Node
object had leftChild
and rightChild
as nil
, then you would refer that node as a leaf.
In this section, you’ll implement a general-purpose tree. This is a fancy way of saying a tree without any kind of restrictions (like how many children each node may have, or the order of nodes).
Remember that a tree is made up of nodes. So to start, let’s create a basic node class. Create a new Swift playground and add the following empty class:
class Node { } |
Of course, a node isn’t much use without a value associated with it.
For simplicity, you’ll specialize this tree to manage string data. Update your current implementation of the Node
to the following:
class Node { var value: String init(value: String) { self.value = value } } |
You’ve declared a property named value
of type String
. You also declare an initializer, which is required for initializing all non-optional stored properties for your class.
In addition to a value, each node needs to have a list of children.
Update your class definition to the following:
class Node { var value: String var children: [Node] = [] // add the children property init(value: String) { self.value = value } } |
You simply declare children as an array of nodes. Each child represents a node that is 1 level deeper than the current node.
Sometimes it’s handy for each node to have a link to its parent node as well. Children are the nodes below a given node; the parent is the node above. A node may only have one parent, but can have multiple children.
Update the implementation of your Node
class to the following:
class Node { var value: String var children: [Node] = [] weak var parent: Node? // add the parent property init(value: String) { self.value = value } } |
Note that you’ve made parent
an optional. This is because not all nodes have parents – such as the root node in a tree.
To handle insertion to your tree, you’ll declare a addChild()
method in your Node
class. Update the implementation of your class to the following:
class Node { var value: String var children: [Node] = [] weak var parent: Node? init(value: String) { self.value = value } func addChild(node: Node) { children.append(node) node.parent = self } } |
It’s best to understand how addChild()
works by using it on a live playground. Outside the implementation of your class, write the following into your playground:
let beverages = Node(value: "beverages") let hotBeverages = Node(value: "hot") let coldBeverages = Node(value: "cold") beverages.addChild(hotBeverages) beverages.addChild(coldBeverages) |
Hierarchical structures are natural candidates for tree structures, so here you’ve defined 3 different nodes and organized them into a logical hierarchy. This corresponds to the following structure:
Ready for a quick test of knowledge?
Try writing the code to extend your tree to match the following diagram:
The solution is provided in the spoiler section down below, but try it yourself first!
Solution Inside: Solution | SelectShow> | |
---|---|---|
|
Verifying a large tree structure can be hard without any console logging. After defining your tree structure, try to log your result in the console by printing the tree:
print(beverages) // <- try to print it! |
You can bring up the console by pressing the following keys in combination: Command-Shift-Y. You should see the name of your class printed onto the console.
Node
How silly! Unfortunately the compiler doesn’t know the best print your custom Swift object, unless you tell it.
To aid the compiler, you’ll need to make Node
adopt the CustomStringConvertible
protocol. To do this, add the following just below the implementation of your Node
class:
// 1 extension Node: CustomStringConvertible { // 2 var description: String { // 3 var text = "\(value)" // 4 if !children.isEmpty { text += " {" + children.map { $0.description }.joinWithSeparator(", ") + "} " } return text } } |
This code is relatively straight forward:
description
, with the String
type.description
property. This is a computed property, a read only property that returns a String
.text
variable. This will hold the entire string. For now, you’ve given it the current node’s value.Now, when you call the print
your Node
classes, you’ll get a nice representation of your tree structure like this:
"beverages {hot {tea {black, green, chai} , coffee, cocoa} , cold {soda {ginger ale, bitter lemon} , milk} } \n"
Note: If you the mapping syntax confuses you, here’s what you could have written instead:
if !children.isEmpty { text += " {" for child in children { text += child.description + ", " } text += "} " } |
map
is a method that acts on a collection of objects, such as arrays. Defined by types that adopt the SequenceType
protocol, map
allows you to perform operations on every element of the array. In your case, you’re iterating through the children and performing a string append operation.
To learn more about map
, you can read about it in this tutorial: Introduction to Functional Programming in Swift.
The general-purpose tree shown here is great for describing hierarchical data, but it really depends on your application in regards to what kind of extra functionality it needs to have. For example, you could use the Node
class to determine if the tree contains a particular value.
To facilitate a search algorithm for this general-purpose tree, add the following extension at the bottom of your playground file:
extension Node { // 1 func search(value: String) -> Node? { // 2 if value == self.value { return self } // 3 for child in children { if let found = child.search(value) { return found } } // 4 return nil } } |
The code here is relatively straightforward:
self
, which is the current node.children
array. You’ll call each child’s search method, which will recursively iterate through all the children. If any of the nodes have a match, your if let
statement will evaluate true and return the node.Let’s give our search method a try! At the bottom of your playground file, write the following:
beverages.search("cocoa") // returns the "cocoa" node beverages.search("chai") // returns the "chai" node beverages.search("bubbly") // returns nil |
Nice work so far! You’ve learned how to implement a general-purpose tree that stores String
values. You’ve defined a nice way to print your tree into the console, and also provided searching capabilities to your Node
class.
Trees are a great way to lay out your hierarchical structure of strings, but what if you wanted to store integers instead?
You could modify the Node
class to take in an Int
:
class Node { var value: Int // ... } |
but then your old implementation that accepts String
value is lost. Ideally, you’d want to create a Node
class that could accept all types of objects, whether it is an Int
, Double
, Float
, or even a custom class of your own. To facilitate the generic usage of your Node
class, you’ll have to dive in the world of generics!
The idea of generics is to abstract away the type requirements from algorithm and data structures. This allows you to keep the idea generalized and reusable. Whether an object would behave well in a tree (or any other data structure) should not be whether it is an Int
or a String
, but rather something more intrinsic; In the context of trees, any type that behaves well in a hierarchy are good candidates to be used in a tree.
Time to make some breaking changes! Update the implementation of your Node
class to the following:
// 1. class Node<T> { // 2. var value: T weak var parent: Node? // 3. var children: [Node] = [] // 4. init(value: T) { self.value = value } // 5. func addChild(node: Node) { children.append(node) node.parent = self } } |
Right away, you should see some compiler errors. But fear not, you’ll clear those errors as you finish the generalizing your Node
implementation for all types. Here’s what you’ve done here:
Node
class to take a generic type T
. The <>
syntax around T
is what alerts the compiler that your intention of using generics.Node
class to take in values of any type, so you’ll constrain your value
property to be type T
rather than Int
or String
.T
.addChild
method to take in Node
objects of any type matching the current type of Node
So far so good. Next, find the extension that contains the search
method and update it to use generics:
// 1. extension Node where T: Equatable { // 2. func search(value: T) -> Node? { if value == self.value { return self } for child in children { if let found = child.search(value) { return found } } return nil } } |
You’ve made two changes here:
Equatable
before it can utilize the search
method.value
parameter to be of generic type.Your code should compile now, so let’s test this out! At the bottom of your playground file, add the following code to verify that your generic tree is working:
let number = Node(value: 5) |
Congratulations, you’ve just create a general-purpose tree that works for all types of objects!
You’ve created a very basic tree here, but there are many different ways to construct trees. For example:
parent
property at all.
To learn more about these kinds of trees and more, check out this list of articles in the Swift Algorithm Club repo:
I hope you enjoyed this tutorial on making a Swift Tree data structure!
It’s in your best interest to know about algorithms and data structures – they’re solutions to many real world problems, and are frequently asked as interview questions. Plus it’s fun!
So stay tuned for many more tutorials from the Swift Algorithm club in the future. In the meantime, if you have any questions on implementing trees in Swift, please join the forum discussion below!
The post Swift Algorithm Club: Swift Tree Data Structure appeared first on Ray Wenderlich.
Discover how to get started with iOS 10's new property animators.
The post iOS 10 Screencast: Property Animators: Timing Functions appeared first on Ray Wenderlich.
Networking has played a huge role in Android apps since API level 1. Most apps don’t work in isolation; rather, they connect to an online service to retrieve data or perform other networking functions.
In this Android networking tutorial, you will create a simple app which connects to the GitHub API to retrieve and display a list of repositories.
In the process, you will learn about the following:
Download the starter project for this tutorial and extract the project. Open the starter project in Android Studio by selecting Open an existing Android Studio project from the Quick Start menu:
You can also use File\Open. Navigate to and select the starter project folder.
Open Util.java and look inside; this file contains a helper method which you’ll use to convert a JSON string to a list of repository objects. Repository.java is your model class that represents a Github repository.
Build and run the project to see what you have to work with:
To perform network operations in Android, your application must include certain permissions.
Open manifest/AndroidManifest.xml, and add the following permissions just before the application
tag:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.INTERNET" /> |
The ACCESS_NETWORK_STATE permission is required for your application to check the network state of the device, while the INTERNET permission is required for your application to access the Internet.
Before you attempt to perform any network operations, it is advisable to check first that the device is connected to a network! :]
Before adding any Java code, you’ll need to configure Android Studio to automatically insert import statements to save you from having to add each one manually.
Go to Android Studio\Preferences\Editor\General\Auto Import, select the Add unambiguous imports on the fly checkbox and click OK.
Open MainActivity.java, and add the following method to the class:
private boolean isNetworkConnected() { ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); // 1 NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); // 2 return networkInfo != null && networkInfo.isConnected(); // 3 } |
isNetworkConnected()
checks that the device has an active Internet connection as follows:
NetworkInfo
class that represents the current network connection. This will be null
if no network is available.It’s a good idea to show an alert that prompts the user to connect to a network if the device isn’t already connected.
You will need to manually add the following import statements to the top of the file since Android Studio can’t automatically determine which packages to include:
import android.content.DialogInterface; import android.support.v7.app.AlertDialog; |
Replace the showListFragment(new ArrayList
line in onCreate()
with the following code:
if (isNetworkConnected()) { mProgressDialog = new ProgressDialog(this); mProgressDialog.setMessage("Please wait..."); mProgressDialog.setCancelable(false); mProgressDialog.show(); startDownload(); } else { new AlertDialog.Builder(this) .setTitle("No Internet Connection") .setMessage("It looks like your internet connection is off. Please turn it " + "on and try again") .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { } }).setIcon(android.R.drawable.ic_dialog_alert).show(); } |
This displays a progress dialog and calls startDownload()
if the device is connected to a network; if it isn’t connected, it displays an alert to the user instead.
Build and run your app; if your device is connected to a network, the app will show a ProgressDialog like so:
Now turn off all network connections on your device and start the app again. The app should now show an alert with the Internet connection error message:
When you have an app that retrieves huge amounts of data, you might want to restrict network connections to particular network types, such as Wi-Fi. You can do this using getType()
on the NetworkInfo object.
Add the following method to MainActivity:
private boolean isWifiConnected() { ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); return networkInfo != null && (ConnectivityManager.TYPE_WIFI == networkInfo.getType()) && networkInfo.isConnected(); } |
The above method returns true
if the device is connected to a Wi-Fi network.
Replace the isNetworkConnected()
call in onCreate()
with the following:
isWifiConnected() |
Build and run your app; if your device is connected to a wifi network, the app will display the following progress dialog:
Now turn off the Wi-Fi connection on the device (leaving packet data active) and restart the app. You’ll see an alert with the Internet connection error message:
Android best practice is to handle any long-running tasks that may hang your application in a thread separate from the user interface thread. Network operations fall into this category of tasks.
Since Android 3.0 (Honeycomb), the system has been configured to crash with a NetworkOnMainThreadException
exception if the network is accessed in the user interface thread.
The AsyncTask class lets you perform asynchronous tasks in the background and publish results on the UI thread without any thread manipulation.
Since AsyncTask is an abstract class, you must subclass it before you can use it.
To create a new class, select the package in which you want to create the class – in this case, reposearch. Next, select File/New/Java Class:
In the Create New Class dialog, enter DownloadRepoTask as the name of the class and click OK:
Replace the class declaration with the following code:
public class DownloadRepoTask extends AsyncTask<String, Void, String> { // 1 @Override protected String doInBackground(String... params) { return null; } // 2 @Override protected void onPostExecute(String result) { } } |
Here’s what’s going on in the code above:
AsyncTask makes it possible to perform (long-running) operations in the background and publish results on the UI thread without having to write threading code. The declaration for DownloadRepoTask contains the keywords extends AsyncTask
. The first parameter in the diamond operator determines the type of the input parameter to the doInBackground method while the third parameter in the diamond operator determines the type of the return value of the onPostExecute method. The second parameter determines the type of the return value of the onProgressUpdate method which is not used here so it is set to Void
.
doInBackground()
is where your background task executes – in this case, the data download task.onPostExecute()
is called is called with the results.Add the following code to startDownload()
in MainActivity.java:
new DownloadRepoTask().execute("https://api.github.com/repositories"); |
This calls an instance of the DownloadRepoTask class, with the parameters required to perform the task passed in the execute call.
Open DownloadRepoTask.java and add the following method:
private String downloadData(String urlString) throws IOException { InputStream is = null; try { URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.connect(); return null; } finally { if (is != null) { is.close(); } } } |
The above code creates and opens a connection to the resource referred to by the URL.
Replace the return
statement in doInBackground()
with the following:
try { return downloadData(params[0]); } catch (IOException e) { e.printStackTrace(); return null; } |
This calls downloadData()
and passes to it the URL you passed in from the task execution.
Now that you’ve created a connection to the resource, you can retrieve the data. You get the data as an InputStream by calling getInputStream()
method on the HttpURLConnection object.
Add the following code to the DownloadRepoTask.java class, just before the return
statement:
is = conn.getInputStream(); |
An InputStream is a readable source of bytes; in this case, it’s the JSON returned from GitHub that you’ll have to decode.
Add the following method to DownloadRepoTask.java:
private String convertToString(InputStream is) throws IOException { BufferedReader r = new BufferedReader(new InputStreamReader(is)); StringBuilder total = new StringBuilder(); String line; while ((line = r.readLine()) != null) { total.append(line); } return new String(total); } |
convertToString()
uses the StringBuilder
class to build a string from the input stream.
Replace the return
statement in downloadData()
with the following:
return convertToString(is); |
You need to pass the downloaded data on to the Activity that started the task. You can use a listener, a type of interface, for this.
To create a new interface, select the package in which you want to create the interface – in this case, the reposearch package. Next, select File/New/Java Class.
In the Create New Class dialog, enter the name of the interface as DownloadCompleteListener, select the Interface option in the Kind select box and click OK.
Add the following method declaration inside DownloadCompleteListener
:
void downloadComplete(ArrayList<Repository> repositories); |
Add implements DownloadCompleteListener
to the beginning of the class declaration of MainActivity.java so it looks like the following:
public class MainActivity extends AppCompatActivity implements DownloadCompleteListener |
Now implement downloadComplete()
in MainActivity.java like so:
@Override public void downloadComplete(ArrayList<Repository> repositories) { } |
Add the following DownloadCompleteListener
to DownloadRepoTask.java:
DownloadCompleteListener mDownloadCompleteListener; |
Next, add a new constructor that takes a DownloadCompleteListener
parameter and uses it to set the DownloadCompleteListener
field in the class:
public DownloadRepoTask(DownloadCompleteListener downloadCompleteListener) { this.mDownloadCompleteListener = downloadCompleteListener; } |
Next, add the following code to onPostExecute:
try { mDownloadCompleteListener.downloadComplete(Util.retrieveRepositoriesFromResponse(result)); } catch (JSONException e) { e.printStackTrace(); } |
This converts and passes the results to the DownloadCompleteListener using downloadComplete()
.
Open MainActivity.java and replace the code in startDownload()
with the following:
new DownloadRepoTask(this).execute("https://api.github.com/repositories"); |
This will start the AsyncTask that you made and direct it to the github repositories URL.
Next, add the following code to downloadComplete()
:
showListFragment(repositories); if (mProgressDialog != null) { mProgressDialog.hide(); } |
The above code receives a list of Repository objects, displays them to the user, then hides the progress dialog.
Make sure the device has a working Internet connection, then build and run:
Cool – your app connected to the GitHub API and retrieved a list of repositories for your perusal!
Performing network operations in Android can be a bit tedious; you have to open and close connections, handle InputStream
conversions and ensure you’re performing the network operation in a background thread.
As usual, the geniuses of the open source community have come to the rescue in the form of open source libraries that take care of the tedious details for you.
There are a number of open source libraries that simplify a lot of Android networking operations. I’ve put together a few notes on the three most popular ones below.
OkHttp is an efficient HTTP client which supports synchronous and asynchronous calls. It handles the opening and closing of connections along with InputStream-to-string conversion. It’s compatible with Android 2.3 and above.
Implementing OkHttp is pretty simple and reduces the overall amount of code needed to make network calls – but there’s a catch.
Since OkHttp is built as a Java library, and not an Android library, it doesn’t take into consideration the Android Framework limitations of only permitting view updates on the main UI thread. To update any views, or post data back to the main thread, you will need to use runOnUiThread()
provided in the Android Activity superclass.
To use OkHttp, you have to first add it as a dependency to your project.
Add the following dependency to build.gradle of the app module:
dependencies { ... compile 'com.squareup.okhttp3:okhttp:3.1.2' } |
Your build.gradle file should look something like the one shown below:
Now rebuild your project:
Open MainActivity.java and add the following method:
private void makeRequestWithOkHttp(String url) { OkHttpClient client = new OkHttpClient(); // 1 okhttp3.Request request = new okhttp3.Request.Builder().url(url).build(); // 2 client.newCall(request).enqueue(new okhttp3.Callback() { // 3 @Override public void onFailure(okhttp3.Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException { final String result = response.body().string(); // 4 MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { try { downloadComplete(Util.retrieveRepositoriesFromResponse(result)); // 5 } catch (JSONException e) { e.printStackTrace(); } } }); } }); } |
The code above does the following:
Replace the code in the startDownload() method with the following:
makeRequestWithOkHttp("https://api.github.com/repositories"); |
Build and run your app, and everything should work as before:
Pretty simple, right?
Volley is an Android library for making fast and easy network calls. Just like OkHttp, it does all the dirty work for you. One of its advantages over OkHttp is that it’s compatible with Android 1.6 and above.
By default, Volley makes all calls in an asynchronous manner. Since it’s an Android library, it doesn’t require any extra work to return data to the main thread or update views.
To use Volley, add the following dependency to build.gradle of the app module and rebuild the project:
dependencies { ... compile 'com.android.volley:volley:1.0.0' } |
Open MainActivity.java and add the following method:
private void makeRequestWithVolley(String url) { RequestQueue queue = Volley.newRequestQueue(this); // 1 StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new com.android.volley.Response.Listener<String>() { // 2 @Override public void onResponse(String response) { try { downloadComplete(Util.retrieveRepositoriesFromResponse(response)); // 3 } catch (JSONException e) { e.printStackTrace(); } } }, new com.android.volley.Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); queue.add(stringRequest); // 4 } |
makeRequestWithVolley
does the following:
StringRequest
with the URL you want to connect to.downloadComplete()
.Replace the code in startDownload()
with the following code:
makeRequestWithVolley("https://api.github.com/repositories"); |
Build and run your project; once again, the network requests should work as before:
Retrofit is an Android and Java library which is great at retrieving and uploading structured data such as JSON and XML. Retrofit makes HTTP requests using OkHttp. Unlike the case of Volley, where you have to convert a JSON string to a Repository object, Retrofit does that conversion for you. Retrofit also lets you specify any of the following libraries for the data conversion:
To use Retrofit, add the following dependencies to build.gradle of the app module and rebuild the project:
dependencies { ... compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' } |
Then create a new interface and name it RetrofitAPI; this will define the HTTP operations.
Add the following code to RetrofitAPI.java:
@GET("/repositories") Call<ArrayList<Repository>> retrieveRepositories(); |
This specifies the request method and the URL for the request call.
Add the following imports to MainActivity.java:
import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; |
Then add the following method:
private void makeRetrofitCalls() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") // 1 .addConverterFactory(GsonConverterFactory.create()) // 2 .build(); RetrofitAPI retrofitAPI = retrofit.create(RetrofitAPI.class); // 3 Call<ArrayList<Repository>> call = retrofitAPI.retrieveRepositories(); // 4 call.enqueue(new Callback<ArrayList<Repository>>() { // 5 @Override public void onResponse(Call<ArrayList<Repository>> call, Response<ArrayList<Repository>> response) { downloadComplete(response.body()); // 6 } @Override public void onFailure(Call<ArrayList<Repository>> call, Throwable t) { Toast.makeText(MainActivity.this, t.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); } }); } |
makeRetrofitCalls()
does the following:
downloadComplete()
.Replace the code in startDownload()
with the following:
makeRetrofitCalls(); |
Build and run your project, and the list of repositories shows up as before:
You’ve explored (and survived!) a crash-course on network operations in Android. :] You can download the final project from this tutorial here.
For more details on the open source projects used in this Android networking tutorial, check out the OkHttp and Retrofit pages on Square’s GitHub pages and the Volley page on the Android developer site.
You can also check out the Networking Operations page on the Android developer site.
I hope you enjoyed this tutorial; if you have any questions or comments, please join the forum discussion below!
The post Android Networking Tutorial: Getting Started appeared first on Ray Wenderlich.
Learn how you can use notification actions to provide visual indicators to user interaction in this iOS 10 screencast.
The post iOS 10 Screencast: Interactive Custom Notifications appeared first on Ray Wenderlich.
Error handling in Swift has come a long way since the patterns in Swift 1 that were inspired by Objective-C. Major improvements in Swift 2 make the experience of handling unexpected states and conditions in your application more straightforward.
Just like other common programming languages, preferred error handling techniques in Swift can vary, depending upon the type of error encountered, and the overall architecture of your app.
This tutorial will take you through a magical example involving wizards, witches, bats and toads to illustrate how best to deal with common failure scenarios. You’ll also look at how to upgrade error handling from projects written in earlier versions of Swift and, finally, gaze into your crystal ball at the possible future of error handling in Swift!
Time to dive straight in (from the the cauldron into the fire!) and discover the various charms of error handling in Swift 2!
There are two starter playgrounds for this tutorial, one for each section. Download Avoiding Errors with nil – Starter.playground and Avoiding Errors with Custom Handling – Starter.playground playgrounds.
Open up the Errors with nil starter playground in Xcode.
Read through the code and you’ll see several classes, structs and enums that hold the magic for this tutorial.
Take note the following parts of the code:
protocol MagicalTutorialObject { var avatar: String { get } } |
This protocol is applied to all classes and structs used throughout the tutorial to provide a visual representation of each object that can be printed to the console.
enum MagicWords: String { case Abracadbra = "abracadabra" case Alakazam = "alakazam" case HocusPocus = "hocus pocus" case PrestoChango = "presto chango" } |
This enumeration denotes magic words that can be used to create a Spell
.
struct Spell: MagicalTutorialObject { var magicWords: MagicWords = .Abracadbra var avatar = "*" } |
This is the basic building block for a Spell
. By default, you initialize its magic words to Abracadabra
.
Now that you’re acquainted with the basics of this supernatural world, you’re ready to start casting some spells.
“Error handling is the art of failing gracefully.”
–Swift Apprentice, Chapter 21 (Error Handing)
Good error handling enhances the experience for end users as well as software maintainers by making it easier to identify issues, their causes and their associated severity. The more specific the error handling is throughout the code, the easier the issues are to diagnose. Error handling also lets systems fail in an appropriate way so as not to frustrate or upset users.
But errors don’t always need to be handled. When they don’t, language features let you avoid certain classes of errors altogether. As a general rule, if you can avoid the possibility of an error, take that design path. If you can’t avoid a potential error condition, then explicit handling is your next best option.
Since Swift has elegant optional-handling capabilities, you can completely avoid the error condition where you expect a value, but no value is provided. As a clever programmer, you can manipulate this feature to intentionally return nil
in an error condition. This approach works best where you should take no action if you reach an error state; i.e., you choose inaction over emergency action.
Two typical examples of avoiding Swift errors using nil
are failable initializers and guard statements.
Failable initializers prevent the creation of an object unless sufficient information has been provided. Prior to Swift 2 (and in other languages), this functionality was typically achieved via the Factory Method Pattern.
An example of this pattern in Swift can be seen in createWithMagicWords
:
static func createWithMagicWords(words: String) -> Spell? { if let incantation = MagicWords(rawValue: words) { var spell = Spell() spell.magicWords = incantation return spell } else { return nil } } |
The above initializer tries to create a spell using magic words provided, but if the words are not magical you return nil
instead.
Inspect the creation of the spells at the very bottom of this tutorial to see this behavior in action:
While first
successfully creates a spell using the magic words "abracadabra"
, "ascendio"
doesn’t have the same effect, and the return value of second
is nil
. (Hey, witches can’t win all the time).
Factory methods are an old-school programming style. There are better ways to achieve the same thing in Swift. You’ll update the Spell
extension to use a failable initializer instead of a factory method.
Delete createWithMagicWords(_:)
and replace it with the following:
init?(words: String) { if let incantation = MagicWords(rawValue: words) { self.magicWords = incantation } else { return nil } } |
Here you’ve simplified the code by not explicitly creating and return the Spell
object.
The lines that assign first
and second
now throw compiler errors:
let first = Spell.createWithMagicWords("abracadabra") let second = Spell.createWithMagicWords("ascendio") |
You’ll need to change these to use the new initializer. Replace the lines above with the following:
let first = Spell(words: "abracadabra") let second = Spell(words: "ascendio") |
After this, all errors should be fixed and the playground should compile without error. With this change your code is definitely tidier – but you can do even better! :]
guard
is a quick way to assert that something is true: for example, if a value is > 0, or if a conditional can be unwrapped. You can then execute a block of code if the check fails.
guard
was introduced in Swift 2 and is typically used to (bubble, toil and trouble) bubble-up error handling through the call stack, where the error will eventually be handled. Guard statements allow early exit from a function or method; this makes it more clear which conditions need to be present for the rest of the processing logic to run.
To clean up Spell
‘s failable initializer further, edit it as shown below to use guard
:
init?(words: String) { guard let incantation = MagicWords(rawValue: words) else { return nil } self.magicWords = incantation } |
With this change, there’s no need need for an else
clause on a separate line and and the failure case is more evident as it’s now at the top of the intializer. Also, the “golden path” is least indented. The “golden path” is the path of execution that happens when everything goes as expected, i.e. no error. Being least indented means that it is easy to read.
Note that the values of first
and second
Spell
constants haven’t changed, but the code is more more streamlined.
Having cleaned up the Spell
initializer and avoided some errors through the clever use of nil
, you’re ready to tackle some more intriciate error handling.
For the next section of this tutorial, open up Avoiding Errors with Custom Handling – Starter.playground.
Take note of the following features of the code:
struct Spell: MagicalTutorialObject { var magicWords: MagicWords = .Abracadbra var avatar = "*" init?(words: String) { guard let incantation = MagicWords(rawValue: words) else { return nil } self.magicWords = incantation } init?(magicWords: MagicWords) { self.magicWords = magicWords } } |
This is the Spell
initializer, updated to match the work you completed in the first section of this tutorial. Also note the presence of the MagicalTutorialObject
protocol, and a second failable initializer, which has been added for convenience.
protocol Familiar: MagicalTutorialObject { var noise: String { get } var name: String? { get set } init() init(name: String?) } |
The Familiar
protocol will be applied to various animals (such as bats and toads) further down in the playground.
This clearly isn’t Hedwig, but still cute nonetheless, no?
struct Witch: MagicalBeing { var avatar = "*" var name: String? var familiar: Familiar? var spells: [Spell] = [] var hat: Hat? init(name: String?, familiar: Familiar?) { self.name = name self.familiar = familiar if let s = Spell(magicWords: .PrestoChango) { self.spells = [s] } } init(name: String?, familiar: Familiar?, hat: Hat?) { self.init(name: name, familiar: familiar) self.hat = hat } func turnFamiliarIntoToad() -> Toad { if let hat = hat { if hat.isMagical { // When have you ever seen a Witch perform a spell without her magical hat on ? :] if let familiar = familiar { // Check if witch has a familiar if let toad = familiar as? Toad { // Check if familiar is already a toad - no magic required return toad } else { if hasSpellOfType(.PrestoChango) { if let name = familiar.name { return Toad(name: name) } } } } } } return Toad(name: "New Toad") // This is an entirely new Toad. } func hasSpellOfType(type: MagicWords) -> Bool { // Check if witch currently has appropriate spell in their spellbook return spells.contains { $0.magicWords == type } } } |
Finally, the witch. Here you see the following:
Witch
is initialized with a name and a familiar, or with a name, a familiar and a hat.Witch
knows a finite number of spells, stored in spells
, which is an array of Spell
objects.Witch
seems to have a penchant for turning her familiar into a toad via the use of the .PrestoChango
spell, within turnFamiliarIntoToad()
.Notice the length and amount of indentation in turnFamiliarIntoToad()
. Also, if anything goes wrong in the method, an entirely new toad will be returned. That seems like a confusing (and incorrect!) outcome for this particular spell. You’ll clean up this code significantly with custom error handling in the next section.
Not to be confused with the Temple of Doom, the Pyramid of Doom is an anti-pattern found in Swift and other languages that can require many levels of nested statements for control flow. It can be seen in turnFamiliarIntoToad()
above – note the six closing parentheses required to close out all the statements, trailing down on a diagonal. Reading code nested in this way requires significant cognitive effort.
Guard statements, as you’ve seen earlier, and multiple optional binding can assist with the cleanup of pyramid-like code. The use of a do-catch
mechanism, however, eliminates the problem altogether by decoupling control flow from error state handling.
do-catch
mechanisms are often found near the following, related, keywords:
throws
do
catch
try
defer
ErrorType
To see these mechanisms in action, you are going to throw multiple custom errors. First, you’ll define the states you wish to handle by listing out everything that could possibly go wrong as an enumeration.
Add the following code to your playground above the definition of Witch
:
enum ChangoSpellError: ErrorType { case HatMissingOrNotMagical case NoFamiliar case FamiliarAlreadyAToad case SpellFailed(reason: String) case SpellNotKnownToWitch } |
Note two things about ChangoSpellError
:
ErrorType
protocol, a requirement for defining errors in Swift.SpellFailed
case, you can handily specify a custom reason for the spell failure with an associated value.ChangoSpellError
is named after the magical utterance of “Presto Chango!” – frequently used by a Witch
when attempting to change a familiar into a Toad
).
OK, ready to make some magic, my pretties? Excellent. Add throws
to the method signature, to indicate that errors may occur as a result of calling this method:
func turnFamiliarIntoToad() throws -> Toad { |
Update it as well on the MagicalBeing
protocol:
protocol MagicalBeing: MagicalTutorialObject { var name: String? { get set } var spells: [Spell] { get set } func turnFamiliarIntoToad() throws -> Toad } |
Now that you have the error states listed, you will rework the turnFamiliarIntoToad()
method, one clause at a time.
First, modify the following statement to ensure the witch is wearing her all-important hat:
if let hat = hat { |
…to the following:
guard let hat = hat else { throw ChangoSpellError.HatMissingOrNotMagical } |
}
at the bottom of the method, or else the playground will compile with errors!
The next line contains a boolean check, also associated with the hat:
if hat.isMagical { |
You could choose to add a separate guard
statement to perform this check, but it would be more clear to group the checks together on a single line for clarity. As such, change the first guard
statement to match the following:
guard let hat = hat where hat.isMagical else { throw ChangoSpellError.HatMissingOrNotMagical } |
Now remove the if hat.isMagical {
check altogether.
In the next section, you’ll continue to unravel the conditional pyramid.
Next up, alter the statement that checks if the witch has a familiar:
if let familiar = familiar { |
…to instead throw a .NoFamiliar
error from another guard
statement:
guard let familiar = familiar else { throw ChangoSpellError.NoFamiliar } |
Ignore any errors that occur for the moment, as they will disappear with your next code change.
On the next line, you return the existing toad if the Witch tries to cast the turnFamiliarIntoToad()
spell on her unsuspecting amphibian, but an explicit error would better inform her of the mistake. Change the following:
if let toad = familiar as? Toad { return toad } |
…to the following:
if familiar is Toad { throw ChangoSpellError.FamiliarAlreadyAToad } |
Note the change from as?
to is
lets you more succinctly check for conformance to the protocol without necessarily needing to use the result. The is
keyword can also be used for type comparison in a more general fashion. If you’re interested in learning more about is
and as
, check out the type casting section of The Swift Programming Language.
Move everything inside the else
clause outside of the else
clause, and delete the else
. It’s no longer necessary!
Finally, the hasSpellOfType(type:)
call ensures that the Witch has the appropriate spell in her spellbook. Change the code below:
if hasSpellOfType(.PrestoChango) { if let toad = f as? Toad { return toad } } |
…to the following:
guard hasSpellOfType(.PrestoChango) else { throw ChangoSpellError.SpellNotKnownToWitch } guard let name = familiar.name else { let reason = "Familiar doesn’t have a name." throw ChangoSpellError.SpellFailed(reason: reason) } return Toad(name: name) |
And now you can remove the final line of code which was a fail-safe. Remove this line:
return Toad(name: "New Toad") |
You now have the following clean and tidy method, ready for use. I’ve provided a few additional comments to the code below, to further explain what the method is doing:
func turnFamiliarIntoToad() throws -> Toad { // When have you ever seen a Witch perform a spell without her magical hat on ? :] guard let hat = hat where hat.isMagical else { throw ChangoSpellError.HatMissingOrNotMagical } // Check if witch has a familiar guard let familiar = familiar else { throw ChangoSpellError.NoFamiliar } // Check if familiar is already a toad - if so, why are you casting the spell? if familiar is Toad { throw ChangoSpellError.FamiliarAlreadyAToad } guard hasSpellOfType(.PrestoChango) else { throw ChangoSpellError.SpellNotKnownToWitch } // Check if the familiar has a name guard let name = familiar.name else { let reason = "Familiar doesn’t have a name." throw ChangoSpellError.SpellFailed(reason: reason) } // It all checks out! Return a toad with the same name as the witch's familiar return Toad(name: name) } |
You simply could have returned an optional from turnFamiliarIntoToad()
to indicate that “something went wrong while this spell was being performed”, but using custom errors more clearly expresses the error states and lets you react to them accordingly.
Now that you have a method to throw custom Swift errors, you need to handle them. The standard mechanism for doing this is called the do-catch
statement, which is similar to try-catch
mechanisms found in other languages such as Java.
Add the following code to the bottom of your playground:
func exampleOne() { print("") // Add an empty line in the debug area // 1 let salem = Cat(name: "Salem Saberhagen") salem.speak() // 2 let witchOne = Witch(name: "Sabrina", familiar: salem) do { // 3 try witchOne.turnFamiliarIntoToad() } // 4 catch let error as ChangoSpellError { handleSpellError(error) } // 5 catch { print("Something went wrong, are you feeling OK?") } } |
Here’s what that function does:
ChangoSpellError
error and handle the error appropriately.After you add the above, you’ll see a compiler error – time to fix that.
handleSpellError()
has not yet been defined, so add the following code above the exampleOne()
function definition:
func handleSpellError(error: ChangoSpellError) { let prefix = "Spell Failed." switch error { case .HatMissingOrNotMagical: print("\(prefix) Did you forget your hat, or does it need its batteries charged?") case .FamiliarAlreadyAToad: print("\(prefix) Why are you trying to change a Toad into a Toad?") default: print(prefix) } } |
Finally, run the code by adding the following to the bottom of your playground:
exampleOne() |
Reveal the Debug console by clicking the up arrow icon in the bottom left hand corner of the Xcode workspace so you can see the output from your playground:
Below is a brief discussion of each of language feature used in the above code snippet.
catch
You can use pattern matching in Swift to handle specific errors or group themes of error types together.
The code above demonstrates several uses of catch
: one where you catch a specific ChangoSpell
error, and one that handles the remaining error cases.
try
You use try
in conjunction with do-catch
statements to clearly indicate which line or section of code may throw errors.
You can use try
in several different ways, one of which is used above:
try
: standard usage within a clear and immediate do-catch
statement. This is used above.try?
: handle an error by essentially ignoring it; if an error is thrown, the result of the statement will be nil
.try!
: similar to the syntax used for force-unwrapping, this prefix creates the expectation that, in theory, a statement could throw an error, in practice the error condition will never occur. try!
can be used for actions such as loading files, where you are certain the required media exists. Like force-unwrap, this construct should be used carefully. Time to check out a try?
statement in action. Cut and paste the following code into the bottom of your playground:
func exampleTwo() { print("") // Add an empty line in the debug area let toad = Toad(name: "Mr. Toad") toad.speak() let hat = Hat() let witchTwo = Witch(name: "Elphaba", familiar: toad, hat: hat) let newToad = try? witchTwo.turnFamiliarIntoToad() if newToad != nil { // Same logic as: if let _ = newToad print("Successfully changed familiar into toad.") } else { print("Spell failed.") } } |
Notice the difference with exampleOne
. Here you don’t care about the output of the particular error, but still capture the fact that one occurred. The Toad
was not created, so the value of newToad
is nil
.
throws
The throws
keyword is required in Swift if a function or method throws an error. Thrown errors are automatically propagated up the call stack, but letting errors bubble too far from their source is considered bad practice. Significant propagation of errors throughout a codebase increases the likelihood errors will escape appropriate handling, so throws
is a mandate to ensure propagation is documented in code – and remains evident to the coder.
rethrows
All examples you’ve seen so far have used throws
, but what about its counterpart rethrows
?
rethrows
tells the compiler that this function will only throw an error when its function parameter throws an error. A quick and magical example can be found below (no need to add this to the playground):
func doSomethingMagical(magicalOperation: () throws -> MagicalResult) rethrows -> MagicalResult { return try magicalOperation() } |
Here doSomethingMagical(_:)
will only throw an error if the magicalOperation
provided to the function throws one. If it succeeds, it returns a MagicalResult
instead.
defer
Although auto-propagation will serve you well in most cases, there are situations where you might want to manipulate the behavior of your application as an error travels up the call stack.
The defer
statement is a mechanism that permits a ‘cleanup’ action to be performed whenever the current scope is exited, such as when a method or function returns. It’s useful for managing resources that need to be tidied up whether or not the action was successful, and so becomes especially useful in an error handling context.
To see this in action, add the following method to the Witch
structure:
func speak() { defer { print("*cackles*") } print("Hello my pretties.") } |
Add the following code to the bottom of the playground:
func exampleThree() { print("") // Add an empty line in the debug area let witchThree = Witch(name: "Hermione", familiar: nil, hat: nil) witchThree.speak() } exampleThree() |
In the debug console, you should see the witch cackle after everything she says.
Interestingly, defer
statements are executed in the opposite order in which they are written.
Add a second defer
statement to speak()
so that a Witch screeches, then cackles after everything she says:
func speak() { defer { print("*cackles*") } defer { print("*screeches*") } print("Hello my pretties.") } |
Did the statements print in the order you expected? Ah, the magic of defer
!
The inclusion of the above statements in Swift brings the language into line with many other popular languages and separates Swift from the NSError
-based approach found in Objective-C. Objective-C errors are, for the most part, directly translated, and the static analyzer in the compiler is excellent for helping you with which errors you need to catch, and when.
Although the do-catch
and related features have significant overhead in other languages, in Swift they are treated like any other statement. This ensures they remain efficient – and effective.
But just because you can create custom errors and throw them around, doesn’t necessarily mean that you should. You really should develop guidelines regarding when to throw and catch errors for each project you undertake. I’d recommend the following:
A couple of ideas for advanced error handling are floating around various Swift forums. One of the most-discussed concepts is untyped propagation.
– from Swift 2.x Error Handling
Whether you enjoy the idea of a major error handling change in Swift 3, or are happy with where things are today, it’s nice to know that clean error handling is being actively discussed and improved as the language develops.
You can download the finished set of playgrounds here for this tutorial.
For further reading, I recommend the following articles, some of which have already been referenced throughout this tutorial:
If you’re keen to see what may lie ahead in Swift 3, I recommend reading through the currently open proposals; see Swift Language Proposals for further details. If you’re feeling adventurous, why not submit your own? :]
Hopefully by now that you’ve been truly enchanted by error handling in Swift. If you have any questions or comments on this tutorial, please join the forum discussion below!
The post Magical Error Handling in Swift appeared first on Ray Wenderlich.
Learn how to use property animators to pause, scrub and reverse complex animations in iOS 10.
The post iOS 10 Screencast: Property Animators: Pause, Scrub & Reverse appeared first on Ray Wenderlich.
Learn how to achieve the ability to send state along with the message content, enabling you to create fully collaborative messaging experiences.
The post iOS 10 Screencast: Collaborative Messaging in iOS 10 appeared first on Ray Wenderlich.
If you want to learn about the advanced capabilities of NSCollectionView
, you’ve come to the right place. This is the second part of a tutorial that covered the basics, and in this Advanced Collection Views in OS X Tutorial, you step deeper into the encompassing world of collection views.
In this OS X tutorial, you’ll learn how:
You need basic knowledge of NSCollectionView
, and you’ll need to know your way around the project from the Collection Views tutorial.
SlidesPro is the app you’re going to build, and it picks up where the previous tutorial left off.
Download the SlidesPro starter project here.
Build and run.
In this section, you’ll walk through the steps needed to add new items to the collection.
You’re not going to be able to add anything to that collection view until you make a way to do it. Good thing you’re a developer! What’s needed here is a button that displays a standard open panel from which you can choose images.
Open Main.storyboard and drag a Push Button into the bottom of the collection view. In the Attributes Inspector, set its Title to Add, and uncheck Enabled.
Select the Editor \ Resolve Auto Layout Issues \ Add Missing Constraints menu item to set the button’s Auto Layout constraints.
Build and run and check if you’ve got a button.
SlidesPro should be set up so that when you select an item, the new item is inserted starting at the index path of whatever image you’ve selected. Then this item and the rest of the section are pushed below the new items.
Accordingly, the add button can only be enabled when an item is selected.
In ViewController
, add an IBOutlet
for the button:
@IBOutlet weak var addSlideButton: NSButton! |
Next, open Main.storyboard and connect the outlet to the button.
You’ll need to track selection changes that will ultimately enable and disable the button inside of highlightItems(_: atIndexPaths:)
, a ViewController
method. When items are selected or deselected it’s called by the two NSCollectionViewDelegate
methods.
To do this, you just need to append one line to highlightItems(_: atIndexPaths:)
:
func highlightItems(selected: Bool, atIndexPaths: Set<NSIndexPath>) { ....... ....... addSlideButton.enabled = collectionView.selectionIndexPaths.count == 1 } |
With this line you enable the button only when one item is selected.
Build and run. Verify that the button is enabled only when an item is selected.
Adding new items to a collection view is a two-stage process. First, you add the items to the model then notify the collection view about the changes.
To update your model, you’ll need to append the following to the ImageDirectoryLoader
class:
func insertImage(image: ImageFile, atIndexPath: NSIndexPath) { let imageIndexInImageFiles = sectionsAttributesArray[atIndexPath.section].sectionOffset + atIndexPath.item imageFiles.insert(image, atIndex: imageIndexInImageFiles) let sectionToUpdate = atIndexPath.section sectionsAttributesArray[sectionToUpdate].sectionLength += 1 sectionLengthArray[sectionToUpdate] += 1 if sectionToUpdate < numberOfSections-1 { for i in sectionToUpdate+1...numberOfSections-1 { sectionsAttributesArray[i].sectionOffset += 1 } } } |
This method inserts the new image to your data model and updates everything so that your model stays in a consistent state.
Add the following methods to ViewController
. The first method is called from the IBAction
method that’s triggered by clicking the add button, and then it’s followed by the action method:
private func insertAtIndexPathFromURLs(urls: [NSURL], atIndexPath: NSIndexPath) { var indexPaths: Set<NSIndexPath> = [] let section = atIndexPath.section var currentItem = atIndexPath.item // 1 for url in urls { // 2 let imageFile = ImageFile(url: url) let currentIndexPath = NSIndexPath(forItem: currentItem, inSection: section) imageDirectoryLoader.insertImage(imageFile, atIndexPath: currentIndexPath) indexPaths.insert(currentIndexPath) currentItem += 1 } // 3 collectionView.insertItemsAtIndexPaths(indexPaths) } @IBAction func addSlide(sender: NSButton) { // 4 let insertAtIndexPath = collectionView.selectionIndexPaths.first! //5 let openPanel = NSOpenPanel() openPanel.canChooseDirectories = false openPanel.canChooseFiles = true openPanel.allowsMultipleSelection = true; openPanel.allowedFileTypes = ["public.image"] openPanel.beginSheetModalForWindow(self.view.window!) { (response) -> Void in guard response == NSFileHandlingPanelOKButton else {return} self.insertAtIndexPathFromURLs(openPanel.URLs, atIndexPath: insertAtIndexPath) } } |
ImageFile
instance is created and added to the model.NSIndexPath
of the selected item defines where the insertion starts.NSOpenPanel
and configures it to only allow the selection of image files and shows it. Open Main.storyboard and connect the addSlide(_:)
IBAction
to the button.
Build and run.
Select the last image in Section 1 — on my system it’s the Desert.jpg slide.
Click the Add button. In the Open panel navigate to the My Private Zoo folder inside the project’s root directory and select all files.
Click Open. The app will insert the new images in Section 1, starting at item 2, where Desert.jpg was before the insertion.
To remove items in SlidesPro you’ll need a remove button, and it should sit next to the add button. The most logical implementation is that it should remove all selected items, hence, this button should be enabled only when one or more items are selected.
And then there’s this detail: multi-selection must be enabled to allow you to work with more than one image at a time.
This section will walk you through adding the button and enabling multi-select.
Open Main.storyboard and select the Collection View. In the Attributes Inspector, check Allows Multiple Selection.
Build and run and verify that multi-selection works.
To expand or reduce a collection’s selection, press and hold the shift or command key while you click on various items. Multi-selections can reach across sections.
Open Main.storyboard, and then drag a Push Button from the Object Library and place it to the left of the Add button.
In the Attributes Inspector, set its Title to Remove, and uncheck Enabled.
Set the button’s Auto Layout constraints by selecting the Editor \ Resolve Auto Layout Issues \ Add Missing Constraints menu item.
Build and run.
Add an IBOutlet
in ViewController
:
@IBOutlet weak var removeSlideButton: NSButton! |
Next, open Main.storyboard and connect the outlet to the button.
In ViewController
, at the end of highlightItems(_: atIndexPaths:)
, add the line to enable/disable the remove button.
func highlightItems(selected: Bool, atIndexPaths: Set<NSIndexPath>) { ....... ....... removeSlideButton.enabled = !collectionView.selectionIndexPaths.isEmpty } |
Build and run, then select an item. Both the add and remove buttons should become enabled. Add more items to the selection; the add button should become disabled while the remove button stays enabled.
Now you’ll add the code that removes items from the collection. As it is with adding, removing is a two-stage process where you must remove images from the model before notifying the collection view about the changes.
To update the model, add the following method at the end of the ImageDirectoryLoader
class:
func removeImageAtIndexPath(indexPath: NSIndexPath) -> ImageFile { let imageIndexInImageFiles = sectionsAttributesArray[indexPath.section].sectionOffset + indexPath.item let imageFileRemoved = imageFiles.removeAtIndex(imageIndexInImageFiles) let sectionToUpdate = indexPath.section sectionsAttributesArray[sectionToUpdate].sectionLength -= 1 if sectionToUpdate < numberOfSections-1 { for i in sectionToUpdate+1...numberOfSections-1 { sectionsAttributesArray[i].sectionOffset -= 1 } } return imageFileRemoved } |
In ViewController
, add the IBAction
method that’s triggered when you click the Remove button:
@IBAction func removeSlide(sender: NSButton) { let selectionIndexPaths = collectionView.selectionIndexPaths if selectionIndexPaths.isEmpty { return } // 1 var selectionArray = Array(selectionIndexPaths) selectionArray.sortInPlace({path1, path2 in return path1.compare(path2) == .OrderedDescending}) for itemIndexPath in selectionArray { // 2 imageDirectoryLoader.removeImageAtIndexPath(itemIndexPath) } // 3 collectionView.deleteItemsAtIndexPaths(selectionIndexPaths) } |
Here’s what happens in there:
Now open Main.storyboard and connect the removeSlide(_:) IBAction
to the button.
This is how the Connections Inspector of View Controller should look after adding the outlets and actions:
Build and run.
Select one or more images and click the Remove button to verify that it successfully removes the items.
One of the best things about OS X is that you can drag and drop items to move or copy them to different apps. Users expect this behavior, so you’d be wise to add it to anything you decide to put out there.
With SlidesPro, you’ll use drag-and-drop to implement the following capabilities:
To support drag-and-drop, you’ll need to implement the relevant NSCollectionViewDelegate
methods, but you have to register the kind of drag-and-drop operations SlidesPro supports.
Add the following method to ViewController
:
func registerForDragAndDrop() { // 1 collectionView.registerForDraggedTypes([NSURLPboardType]) // 2 collectionView.setDraggingSourceOperationMask(NSDragOperation.Every, forLocal: true) // 3 collectionView.setDraggingSourceOperationMask(NSDragOperation.Every, forLocal: false) } |
In here, you’ve:
At the end of viewDidLoad()
, add:
registerForDragAndDrop() |
Build and run.
Try to drag an item — the item will not move. Drag an image file from Finder and try to drop it on the collection view…nada.
I asked you to perform this test so you can see that items aren’t responding to dragging, and nothing related to drag-and-drop works. Why is that? You’ll soon discover.
The first issue is that there needs to be some additional logic to handle the action, so append the following methods to the NSCollectionViewDelegate
extension of ViewController
:
// 1 func collectionView(collectionView: NSCollectionView, canDragItemsAtIndexes indexes: NSIndexSet, withEvent event: NSEvent) -> Bool { return true } // 2 func collectionView(collectionView: NSCollectionView, pasteboardWriterForItemAtIndexPath indexPath: NSIndexPath) -> NSPasteboardWriting? { let imageFile = imageDirectoryLoader.imageFileForIndexPath(indexPath) return imageFile.url.absoluteURL } |
Here’s what’s happening in here:
delegate
. The return value indicates whether the collection view is allowed to initiate the drag for the specified index paths. You need to be able to drag any item, so you return unconditionally true
.NSPasteboardWriting
; in your case it’s NSURL
. Returning nil
prevents the drag.Build and run.
Try to drag an item, the item moves… Hallelujah!
Perhaps I spoke too soon? When you try to drop the item in a different location in the collection view, it just bounces back. Why? Because you did not define the collection view as a Drop Target.
Now try to drag an item and drop it in Finder; a new image file is created matching the source URL. You have made progress because it works to drag-and-drop from SlidesPro to another app!
Add the following property to ViewController
:
var indexPathsOfItemsBeingDragged: Set<NSIndexPath>! |
Add the following methods to the NSCollectionViewDelegate
extension of ViewController
:
// 1 func collectionView(collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAtPoint screenPoint: NSPoint, forItemsAtIndexPaths indexPaths: Set<NSIndexPath>) { indexPathsOfItemsBeingDragged = indexPaths } // 2 func collectionView(collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath?>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionViewDropOperation>) -> NSDragOperation { // 3 if proposedDropOperation.memory == NSCollectionViewDropOperation.On { proposedDropOperation.memory = NSCollectionViewDropOperation.Before } // 4 if indexPathsOfItemsBeingDragged == nil { return NSDragOperation.Copy } else { return NSDragOperation.Move } } |
Here’s a section-by-section breakdown of this code:
optional
method is invoked when the dragging session is imminent. You’ll use this method to save the index paths of the items that are dragged. When this property is not nil
, it’s an indication that the Drag Source is the collection view.Move
. When the Dragging Source is another app, the operation is Copy
.Build and run.
Drag an item. After you move it, you’ll see some weird gray rectangle with white text. As you keep moving the item over the other items, the same rectangle appears in the gap between the items.
What is happening?
Inside of ViewController
, look at the DataSource
method that’s invoked when the collection view asks for a supplementary view:
func collectionView(collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> NSView { let view = collectionView.makeSupplementaryViewOfKind(NSCollectionElementKindSectionHeader, withIdentifier: "HeaderView", forIndexPath: indexPath) as! HeaderView view.sectionTitle.stringValue = "Section \(indexPath.section)" let numberOfItemsInSection = imageDirectoryLoader.numberOfItemsInSection(indexPath.section) view.imageCount.stringValue = "\(numberOfItemsInSection) image files" return view } |
When you start dragging an item, the collection view’s layout asks for the interim gap indicator’s supplementary view. The above DataSource
method unconditionally assumes that this is a request for a header view. Accordingly, a header view is returned and displayed for the inter-item gap indicator.
None of this is going to work for you so replace the content of this method with:
func collectionView(collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> NSView { // 1 let identifier: String = kind == NSCollectionElementKindSectionHeader ? "HeaderView" : "" let view = collectionView.makeSupplementaryViewOfKind(kind, withIdentifier: identifier, forIndexPath: indexPath) // 2 if kind == NSCollectionElementKindSectionHeader { let headerView = view as! HeaderView headerView.sectionTitle.stringValue = "Section \(indexPath.section)" let numberOfItemsInSection = imageDirectoryLoader.numberOfItemsInSection(indexPath.section) headerView.imageCount.stringValue = "\(numberOfItemsInSection) image files" } return view } |
Here’s what you did in here:
identifier
according to the kind
parameter received. When it isn’t a header view, you set the identifier
to the empty String
. When you pass to makeSupplementaryViewOfKind
an identifier
that doesn’t have a matching class or nib, it will return nil
. When a nil
is returned, the collection view uses its default inter-item gap indicator. When you need to use a custom indicator, you define a nib (as you did for the header) and pass its identifier instead of the empty string.makeItemWithIdentifier
and makeSupplementaryViewOfKind
methods. The return value specified is NSView
, but these methods may return nil
so the return value should be NSView?
— the question mark is part of the value.
Build and run.
Now you see an unmistakable aqua vertical line when you drag an item, indicating the drop target between the items. It’s a sign that the collection view is ready to accept the drop.
Well…it’s sort of ready. When you try to drop the item, it still bounces back because the delegate methods to handle the drop are not in place yet.
Append the following method to ImageDirectoryLoader
:
// 1 func moveImageFromIndexPath(indexPath: NSIndexPath, toIndexPath: NSIndexPath) { // 2 let itemBeingDragged = removeImageAtIndexPath(indexPath) let destinationIsLower = indexPath.compare(toIndexPath) == .OrderedDescending var indexPathOfDestination: NSIndexPath if destinationIsLower { indexPathOfDestination = toIndexPath } else { indexPathOfDestination = NSIndexPath(forItem: toIndexPath.item-1, inSection: toIndexPath.section) } // 3 insertImage(itemBeingDragged, atIndexPath: indexPathOfDestination) } |
Here’s what’s going on in there:
Finish things off here by adding the following methods to the NSCollectionViewDelegate
extension in ViewController
:
// 1 func collectionView(collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: NSIndexPath, dropOperation: NSCollectionViewDropOperation) -> Bool { if indexPathsOfItemsBeingDragged != nil { // 2 let indexPathOfFirstItemBeingDragged = indexPathsOfItemsBeingDragged.first! var toIndexPath: NSIndexPath if indexPathOfFirstItemBeingDragged.compare(indexPath) == .OrderedAscending { toIndexPath = NSIndexPath(forItem: indexPath.item-1, inSection: indexPath.section) } else { toIndexPath = NSIndexPath(forItem: indexPath.item, inSection: indexPath.section) } // 3 imageDirectoryLoader.moveImageFromIndexPath(indexPathOfFirstItemBeingDragged, toIndexPath: toIndexPath) // 4 collectionView.moveItemAtIndexPath(indexPathOfFirstItemBeingDragged, toIndexPath: toIndexPath) } else { // 5 var droppedObjects = Array<NSURL>() draggingInfo.enumerateDraggingItemsWithOptions(NSDraggingItemEnumerationOptions.Concurrent, forView: collectionView, classes: [NSURL.self], searchOptions: [NSPasteboardURLReadingFileURLsOnlyKey : NSNumber(bool: true)]) { (draggingItem, idx, stop) in if let url = draggingItem.item as? NSURL { droppedObjects.append(url) } } // 6 insertAtIndexPathFromURLs(droppedObjects, atIndexPath: indexPath) } return true } // 7 func collectionView(collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAtPoint screenPoint: NSPoint, dragOperation operation: NSDragOperation) { indexPathsOfItemsBeingDragged = nil } |
Here’s what happens with these methods:
ViewController
as Add with URLs obtained from the NSDraggingInfo
.indexPathsOfItemsBeingDragged
.Build and run.
Now it’s possible to move a single item to a different location in the same section. Dragging one or more items from another app should work too.
The current implementation of drag-and-drop in SlidesPro doesn’t support drop across sections. Also, multi-selection is supported only for a drop outside SlidesPro. To disable in UI, these unsupported capabilities change the else
part of the second if
statement to:
func collectionView(collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath?>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionViewDropOperation>) -> NSDragOperation { if proposedDropOperation.memory == NSCollectionViewDropOperation.On { proposedDropOperation.memory = NSCollectionViewDropOperation.Before } if indexPathsOfItemsBeingDragged == nil { return NSDragOperation.Copy } else { let sectionOfItemBeingDragged = indexPathsOfItemsBeingDragged.first!.section // 1 if let proposedDropsection = proposedDropIndexPath.memory?.section where sectionOfItemBeingDragged == proposedDropsection && indexPathsOfItemsBeingDragged.count == 1 { return NSDragOperation.Move } else { // 2 return NSDragOperation.None } } } |
.None
Build and run. Try dragging an item from one section to another. The drop indicator does not present itself, meaning a drop is impossible.
Now drag a multi-selection. While inside the bounds of the collection view there is no drop indicator; however, drag it to Finder, and you’ll see that a drop is allowed.
In the previous section, you noticed an issue with highlighting.
For the sake of sanity in this discussion, the item being moved will be Item-1. After Item-1 lands at a new position it stays highlighted, and the Add and Remove buttons are enabled, but the selection is empty.
To confirm this is true, select any item — Item-2. It highlights as expected, but Item-1 stays highlighted. It should have been deselected and the highlight removed when you selected Item-2.
Click anywhere between the items to deselect everything. Item-2’s highlight goes away, the Add and Remove buttons are disabled, as they should be for no selection, but Item-1 is still highlighted.
selectionIndexPaths
property. To debug, you can insert print statements to show the value of this property.
So what’s going wrong here?
Apparently, the collection view successfully deselects Item-1, but the collectionView(_:didDeselectItemsAtIndexPaths: )
delegate method is not called to remove the highlight and disable the buttons.
In NSCollectionView.h
, the comments for the above method and its companion for the select action say, “Sent at the end of interactive selection…”. Hence, these notifications are sent only when you select/deselect via UI.
Here’s your answer, Sherlock: The deselection behavior that should occur when you’re moving an item is performed programmatically via the deselectItemsAtIndexPaths(_:)
method of NSCollectionView
.
You’ll need to override this method.
Go to File \ New \ File… and create a new Cocoa Class by the name CollectionView make it a subclass of NSCollectionView and put it in the Views group.
The template may add a drawRect(_:)
— make sure to delete it.
Add the following method to CollectionView
:
override func deselectItemsAtIndexPaths(indexPaths: Set<NSIndexPath>) { super.deselectItemsAtIndexPaths(indexPaths) let viewController = delegate as! ViewController viewController.highlightItems(false, atIndexPaths: indexPaths) } |
The method calls its super implementation followed by a call to highlightItems(_:atIndexPaths:)
of its delegate, allowing ViewController
to highlight/unhighlight items and enable/disable buttons respectively.
Open Main.storyboard and select the Collection View. In the Identity Inspector, change Class to CollectionView.
Build and run.
Move an item inside the collection to a different location. Nothing shows as highlighted and buttons disable as expected. Case closed.
NSCollectionView
, as a subclass of NSView
, can perform animations via the animator proxy. It’s as easy as adding a single word in your code before an operation such as removal of items.
At the end of the removeSlide(_:)
method in ViewController
, replace this:
collectionView.deleteItemsAtIndexPaths(selectionIndexPaths) |
With this:
collectionView.animator().deleteItemsAtIndexPaths(selectionIndexPaths) |
Build and run.
Select several items and click the Remove button. Watch as the items glide to take up their new positions on the screen.
The default duration is a quarter of a second. To experience a really cool and beautiful effect, add a setting for the duration of the animation at a higher value. Place it above the line you just added:
NSAnimationContext.currentContext().duration = 1.0 collectionView.animator().deleteItemsAtIndexPaths(selectionIndexPaths) |
Build and run, and then remove some items. Cool effect, isn’t it?
You can do the same for insertItemsAtIndexPaths
when you’re adding items, as well as for moveItemAtIndexPath
when moving an item.
When you scroll a collection view with section headers, the first element of a given section that vanishes at the top of the screen is its header.
In this section, you’ll implement Sticky Headers, so the top-most section header will pin itself to the top of the collection view. It will hold its position until the next section header bumps it out of the way.
To make this effect reality, you’ll subclass NSCollectionViewFlowLayout
.
Go to File \ New \ File… and create a new Cocoa Class named StickyHeadersLayout as a subclass of NSCollectionViewFlowLayout, and put it in the Layout group.
In ViewController
, change the first line of configureCollectionView()
to:
let flowLayout = StickyHeadersLayout() |
Now implement sticky headers by adding the following method to the empty body of the StickyHeadersLayout
class:
override func layoutAttributesForElementsInRect(rect: NSRect) -> [NSCollectionViewLayoutAttributes] { // 1 var layoutAttributes = super.layoutAttributesForElementsInRect(rect) // 2 let sectionsToMoveHeaders = NSMutableIndexSet() for attributes in layoutAttributes { if attributes.representedElementCategory == .Item { sectionsToMoveHeaders.addIndex(attributes.indexPath!.section) } } // 3 for attributes in layoutAttributes { if let elementKind = attributes.representedElementKind where elementKind == NSCollectionElementKindSectionHeader { sectionsToMoveHeaders.removeIndex(attributes.indexPath!.section) } } // 4 sectionsToMoveHeaders.enumerateIndexesUsingBlock { (index, stop) -> Void in let indexPath = NSIndexPath(forItem: 0, inSection: index) let attributes = self.layoutAttributesForSupplementaryViewOfKind(NSCollectionElementKindSectionHeader, atIndexPath: indexPath) if attributes != nil { layoutAttributes.append(attributes!) } } for attributes in layoutAttributes { // 5 if let elementKind = attributes.representedElementKind where elementKind == NSCollectionElementKindSectionHeader { let section = attributes.indexPath!.section let attributesForFirstItemInSection = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: 0, inSection: section)) let attributesForLastItemInSection = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: collectionView!.numberOfItemsInSection(section) - 1, inSection: section)) var frame = attributes.frame // 6 let offset = collectionView!.enclosingScrollView?.documentVisibleRect.origin.y // 7 let minY = CGRectGetMinY(attributesForFirstItemInSection!.frame) - frame.height // 8 let maxY = CGRectGetMaxY(attributesForLastItemInSection!.frame) - frame.height // 9 let y = min(max(offset!, minY), maxY) // 10 frame.origin.y = y attributes.frame = frame // 11 attributes.zIndex = 99 } } // 12 return layoutAttributes } |
Okay, there’s a lot happening in there, but it makes sense when you take it section by section:
NSMutableIndexSet
first aggregates all the sections that have at least one visible item. layoutAttributes
, leaving only the sections with “Missing Headers” in the set.layoutAttributes
.layoutAttributes
and process only the headers. maybeY = max(offset!, minY)
: When the top of the section is above the visible area this pins (or pushes down) the header to the top of the visible area.y = min(maybeY, maxY)
: When the space between the bottom of the section to the top of the visible area is less than header height, it shows only the part of the header’s bottom that fits this space.Add the following method to StickyHeadersLayout
:
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } |
You always return true
because you want the to invalidate the layout as the user scrolls.
Build and run.
Scroll the collection to see your sticky headers in action.
Download the final version of SlidesPro here.
In this Advanced Collection Views in OS X Tutorial you covered a lot of ground! You took the collection view from a rudimentary app to one that features the kinds of bells and whistles any Mac user would expect.
After all your hard work, you’re able to add and remove items, reorder them, and troubleshoot and correct highlighting/selection issues. You took it to the next level by adding in animations and implemented sticky headers to give SlidesPro a very polished look.
Most impressively, you now know how to build a functional, elegant collection view in OS X. Considering that the documentation for these is fairly limited, it’s a great skill to have.
Some of the topics that were not covered neither here nor in the basic tutorial are:
NSCollectionViewLayout
One of the top resources recommended at the end of the basic tutorial is this excellent video tutorial series Custom Collection View Layout from Mic Pringle. Although it’s an iOS series, you can find lots of useful information that’s relevant to collection views in OS X as well.
I hope you found this tutorial most helpful! Let’s talk about it in the forums. I look forward to your questions, comments and discoveries!
The post Advanced Collection Views in OS X Tutorial appeared first on Ray Wenderlich.
Discover how to use the new notification service extension point which has the ability to intercept push notifications before they're delivered to the user.
The post iOS 10 Screencast: Manipulating Push Notifications with Service Extensions appeared first on Ray Wenderlich.
Join Mic, Jake, and Gemma as they discuss the trials and tribulations of upgrading the Harry’s app to Swift 3, before moving on to chat about Apple’s new Swift Playgrounds app, and the potential for it to become a great learning platform.
[Subscribe in iTunes] [RSS Feed]
This episode was brought to you by those kind folks over at Pyze.
The App stores are crowded and very few apps are making money!
Pyze has developed a free mobile intelligence platform that automates app growth so all apps can develop and retain loyal users thru personalized engagement.
Pyze provides behavior-based real-time insights and automates touch points to build meaningful relationships with each user – all without the time, effort and cost required by today’s big data analytics solutions.
Pyze delivers intelligence-driven marketing and recommendations to cultivate loyalty, increase engagement and grow monetization.
If you’ve got an app, you have to try Pyze, its free and can help grow your app.
Use Pyze and start automating your app’s growth for free.
Interested in sponsoring a podcast episode? We sell ads via Syndicate Ads, check it out!
We hope you enjoyed this episode of our podcast. Be sure to subscribe in iTunes to get notified when the next episode comes out.
We’d love to hear what you think about the podcast, and any suggestions on what you’d like to hear in future episodes. Feel free to drop a comment here, or email us anytime at podcast@raywenderlich.com.
The post Upgrading to Swift 3, and Swift Playgrounds – Podcast S06 E04 appeared first on Ray Wenderlich.