Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all 4374 articles
Browse latest View live

Screencast: Introduction to ARKit: Adding 3D Models


Screencast: Introduction to ARKit: Measuring Distances

How to Create a Simple Game in Unreal Engine 4

$
0
0

How to Create a Simple Game in Unreal Engine 4

If you’re starting out in game development, one of the common pieces of advice is to create a basic game. This is because it teaches you how to create simple mechanics and how objects interact with each other.

In this tutorial, you will create a first-person game that continues indefinitely. You will learn how to:

  • Move the player forward continuously
  • Generate obstacles the player must avoid
  • Randomize the obstacles to create variations
  • Create a restart button that displays when the player hits an obstacle

At the end, you will have a game that looks like this:

Please note, you will be using Blueprints and UMG in this tutorial. If you need a refresher, check out our Blueprints and UI tutorial.

Getting Started

Download the starter project and unzip it. Go to the project folder and open InfiniteMatrix.uproject.

Note: If you get a message saying that the project was created with an earlier version of the Unreal editor, that’s OK (the engine is updated frequently). You can either choose the option to open a copy, or the option to convert in place.

Press Play to test out the movement controls. You can move vertically and horizontally by moving your mouse.

The first thing you will do is make the player move forward continuously.

Moving the Player Forward

Navigate to the Blueprints folder and open BP_Player.

To move the player forward, you will add an offset to the player’s location each frame.

First, you need to create a variable to define the speed of the player’s forward movement. Create a Float variable named ForwardSpeed and set its default value to 2000.

Next, make sure you are in the Event Graph and then locate the Event Tick node. Create the following setup:

By multiplying ForwardSpeed with Delta Seconds, you get a frame rate independent result.

Note: If you aren’t familiar with frame rate independence, please read our Blueprints tutorial. We cover it in the Frame Rate Independence section.

Next, you will use this result to move the player along a single axis.

Moving Along a Single Axis

To move the player, create an AddActorWorldOffset node. Set Sweep to true by left-clicking its checkbox.

If you try to connect the Float result to the Delta Location input, Unreal will automatically convert it to a Vector.

However, this will put the Float value into the X, Y and Z components of the Vector. For this game, the forward movement should only be along the X-axis. Luckily, you can split a Vector into three Float components.

Make sure the Delta Location pin of the AddActorWorldOffset node has no connections. Right-click the Delta Location pin and select Split Struct Pin.

Finally, connect everything like so:

Let’s recap:

  1. Every frame, the game will multiply ForwardSpeed and Delta Seconds to get a frame rate independent result
  2. The AddActorWorldOffset will use the result to move the player along the X-axis
  3. Since Sweep is enabled, the player will stop moving forward if anything blocks it

Click Compile and then go back to the main editor. If you press Play, you will move through the tunnel.

Instead of placing tunnels manually, you can create a Blueprint that spawns tunnels automatically.

Creating the Tunnel Spawner

Go to the Content Browser and make sure you are in the Blueprints folder. Create a new Blueprint Class with Actor as the parent class. Name it BP_TunnelSpawner and then open it.

Since the game will be spawning tunnels constantly, it’s a good idea to create a spawning function. Go to the My Blueprint panel and create a new function named SpawnTunnel. The purpose of this function will be to spawn a tunnel at a provided location.

To pass a location to the function, the function needs an input parameter. These will appear as input pins when you call the function.

They will also appear as output pins on the Entry node of the function.

Let’s go ahead and create an input parameter. Make sure you are in the graph for the SpawnTunnel function. Select the Entry node and then go to the Details panel. Click the + sign next to the Inputs section.

Rename the input parameter to SpawnLocation and change its type to Vector.

To spawn a tunnel, add a Spawn Actor From Class node. Click the drop-down located to the right of the Class pin and select BP_Tunnel.

To set the spawn location, right-click the Spawn Transform pin and select Split Struct Pin. Afterwards, link the Spawn Actor From Class node to the Entry node like so:

Now, whenever you call the SpawnTunnel function, it will spawn an instance of BP_Tunnel at the provided location.

Let’s test it out!

Testing the Tunnel Spawner

Switch to the Event Graph and locate the Event BeginPlay node. Add a SpawnTunnel node and connect it to the Event BeginPlay node.

On the SpawnTunnel node, set Spawn Location to (2000, 0, 500).

Now, when the game starts, it will spawn a tunnel up and away from the player. Click Compile and then go back to the main editor.

First, delete BP_Tunnel from the level. Do this by left-clicking on BP_Tunnel in the World Outliner. Afterwards, press the Delete key to remove it from the level.

Next, go to the Content Browser. Left-click and drag BP_TunnelSpawner into the Viewport. This will add an instance of it to the level.

If you press Play, the game will spawn a tunnel above and away from the player.

Once you are done testing, go back to BP_TunnelSpawner. Reset the Spawn Location of the SpawnTunnel node to (0, 0, 0).

Afterwards, click Compile and then go back to the main editor.

In the next section, you will set up functionality for BP_Tunnel.

Setting up the Tunnel Blueprint

BP_Tunnel will be responsible for two things. The first is detecting when the game should spawn a new tunnel. To do that, you will create a trigger zone. Once triggered, BP_Tunnel will tell BP_TunnelSpawner to spawn a new tunnel. By doing this, you can create the illusion of an endless tunnel.

The second thing it will do is define a spawn point. BP_TunnelSpawner will then use this point as the next spawn location.

Let’s start with creating the trigger zone.

Creating the Trigger Zone

Open BP_Tunnel and then go to the Components panel. Add a Box Collision component and name it TriggerZone.

The collision area is quite small at the moment. Go to the Details panel and locate the Shape section. Set the Box Extent property to (32, 500, 500).

Next, set the Location property to (2532, 0, 0). This will place TriggerZone right at the end of the tunnel mesh. This means a new tunnel should only spawn when the player reaches the end of a tunnel.

Now, it’s time to create the spawn point

Creating the Spawn Point

To define the location of the spawn point, you can use a Scene component. These components are perfect for defining locations because they only contain a Transform. They are also visible in the Viewport so you can see where your spawn point is.

Go to the Components panel and make sure you don’t have anything selected. Add a Scene component and rename it to SpawnPoint.

The tunnel mesh is 2500 units long on the X-axis so that’s where the attach point should be. Go to the Details panel and set the Location property to (2500, 0, 0).

The next thing to do is to create a function that spawns a tunnel at SpawnPoint.

Spawning Tunnels at the Spawn Point

Click Compile and then switch to BP_TunnelSpawner.

The next BP_Tunnel should spawn at the SpawnPoint of the furthest tunnel. By doing this, the tunnel will always continue.

Since the furthest tunnel is always the last spawned tunnel, you can easily get a reference to it.

Open the graph for SpawnTunnel. Right-click the Return Value pin of the Spawn Actor From Class node. Select Promote to Variable and rename the variable to NewestTunnel.

Now, you will always have a reference to the furthest tunnel.

Next, create a new function and name it SpawnTunnelAtSpawnPoint. Create the following graph:

This setup will get the newest tunnel and the location of its SpawnPoint component. It will then spawn a new tunnel at this location.

In order for BP_Tunnel to communicate with BP_TunnelSpawner, it needs a reference. Without communication, BP_TunnelSpawner won’t know when to spawn the next tunnel.

Creating a Reference to the Tunnel Spawner

Click Compile and then close the SpawnTunnelAtSpawnPoint graph. Afterwards, switch to BP_Tunnel.

Add a new variable and name it TunnelSpawner. Set its Variable Type to BP_TunnelSpawner\Object Reference.

Click Compile and then switch back to BP_TunnelSpawner.

Open the graph for SpawnTunnel and add the indicated nodes:

Now, every tunnel will have a reference to BP_TunnelSpawner.

Next, you will tell BP_TunnelSpawner to spawn the next tunnel when the player enters TriggerZone.

Scripting the Trigger Zone

Click Compile and then switch to BP_Tunnel.

Go to the Components panel and right-click on TriggerZone. Select Add Event\Add OnComponentBeginOverlap. This will add the following node to your Event Graph:

This node will execute whenever another Actor overlaps TriggerZone.

First, you should check if the Actor that overlapped TriggerZone is the player.

Left-click and drag the Other Actor pin. Release left-click on an empty area and select Cast to BP_Player from the menu.

Note: Since a tunnel spawns at the end of another tunnel, it will trigger that tunnel’s TriggerZone. Cast to BP_Player will prevent any further nodes from executing if Other Actor is a tunnel.

Next, add the indicated nodes after the Cast to BP_Player node:

Let’s go through this step-by-step:

  1. When an Actor overlaps the TriggerZone, the On Component Begin Overlap (TriggerZone) node will execute
  2. The Cast to BP_Player node checks if the overlapping Actor is the player
  3. If it is the player, then BP_TunnelSpawner will spawn a new tunnel. Its location will be at the SpawnPoint component of the last spawned tunnel.
  4. Since there is no more use for the old tunnel, the game removes it using the DestroyActor node

Click Compile, go back to the main editor and then press Play. Once you reach the end of a tunnel, the game will spawn a new one.

Although the game is endlessly spawning tunnels, it doesn’t look endless. You can mitigate this by always having a few tunnels visible. Later on, when you combine this with obstacles, the player won’t be able to see the tunnels spawning in.

Spawning More Tunnels

The first thing to do is create a function that spawns a certain number of tunnels.

Open BP_TunnelSpawner and create a new function called SpawnInitialTunnels.

To spawn a specified number of tunnels, you can use a ForLoop node. This node will execute connected nodes a specified amount of times. Add a ForLoop node and connect it to the Entry node.

To make the ForLoop node execute n amount of times, you need to set Last Index to n – 1.

For this tutorial, you will spawn three tunnels. To perform three loops, set the Last Index value to 2.

Note: If you don’t set the First Index or Last Index fields, they will default to 0

When the game starts, the player should always start in a tunnel. To do this, you can spawn the first tunnel at the player’s location.

Spawning the First Tunnel

To determine if the first tunnel has spawned, you can check if NewestTunnel is set. If it is not set, that means the first tunnel has not spawned. This is because NewestTunnel is only set after the game spawns a tunnel.

To perform this check, add an IsValid node (the one with a question mark icon) after the ForLoop node.

Next, get a reference to NewestTunnel and connect it to the Input Object pin of the IsValid node.

If NewestTunnel is not set, the Is Not Valid pin will execute and vice versa.

Add the following and connect it to the Is Not Valid pin of the IsValid node:

This setup will spawn a tunnel at the location of the player Pawn.

Next, you will spawn the subsequent tunnels.

Spawning Subsequent Tunnels

Add a SpawnTunnelAtAttachPoint node and connect it to the Is Valid pin of the IsValid node.

Here is the final graph:

Summary:

  1. The ForLoop node will execute a total of three times
  2. On the first loop, it will spawn a tunnel at the player’s location
  3. During the subsequent loops, it will spawn a tunnel at the SpawnPoint of the newest tunnel

Next, go to the Event Graph and delete the SpawnTunnel node. Afterwards, add a SpawnInitialTunnels node after Event BeginPlay.

Now, when the game starts, it will spawn three tunnels.

Click Compile, go back to the main editor and then press Play. The tunnel is now much longer!

The game isn’t very challenging at the moment so let’s add some obstacles.

Creating Obstacles

Here are the meshes you will use as obstacles:

Open BP_Tunnel and go to the Components panel. Add a Static Mesh component and name it WallMesh.

Go to the Details panel and change its Static Mesh property to SM_Hole_01.

Next, set its Location property to (2470, 0, 0). This will place it at the end of the tunnel.

To make the game more interesting, the walls will also be rotating. Add a new Float variable and name it RotateSpeed. Set the Default Value to 30.

Switch to the Event Graph and locate the Event Tick node. Create the following setup:

This will make WallMesh rotate every frame by the supplied amount.

Click Compile and then go back to the main editor. Press Play to see the spinning walls.

Let’s spice it up by adding some variations to the walls.

Creating Wall Variations

Instead of creating a new Blueprint for every variation, you can just randomize WallMesh.

Open BP_Tunnel and create a new function called RandomizeWall. Afterwards, create the following graph:

As its name suggests, the Set Static Mesh node will set WallMesh to the supplied mesh.

To make a list of static meshes, you can use a Select node.

Left-click and drag the New Mesh pin. Release left-click on an empty area and then add a Select node.

The Select node allows you to set a list of options. The Index input determines what option the Select node outputs.

Since there are four wall meshes available, you need to create two more Option pins. You can do this by right-clicking the Select node and selecting Add Option Pin. Do this until you have four Option pins.

Next, set each option to the following:

  • Option 0: SM_Hole_01
  • Option 1: SM_Hole_02
  • Option 2: SM_Hole_03
  • Option 3: SM_Hole_04

Now, let’s select a random option.

Randomizing the Wall

You can use a Random Integer in Range node to get a random number. This node will return a value that is >= Min and <= Max.

Add a Random Integer in Range node and connect it to the Index pin of the Select node.

Set the Max value to 3. This will give you four possible numbers: 0, 1, 2 and 3.

To create a bit more randomization, let’s add a random rotation to WallMesh. Add the following after the Set Static Mesh node:

This will add a random rotation between 0 and 360 degrees to WallMesh.

Here is the final graph:

Summary:

  1. The Select node provides a list of meshes
  2. A random mesh is chosen using the Random Integer in Range node
  3. The Set Static Mesh node sets the WallMesh to the chosen mesh
  4. The AddLocalRotation node adds a random rotation offset to WallMesh

Click Compile and then close the RandomizeWall graph.

Switch to BP_TunnelSpawner and open the SpawnTunnel graph. Add the highlighted node:

Now, whenever a tunnel spawns, it will have a random wall mesh.

Close the SpawnTunnel graph and then click Compile. Go back to the main editor and press Play to see all the wall variations!

If you hit a wall, you will stop moving forward. However, if you move around and go through a hole, you will start moving forward again.

The next step is to disable forward movement when the player collides with a wall.

Handling Wall Collisions

To enable or disable forward movement, you can use a Boolean variable. These only have two states: true and false.

Open BP_Player and then create a new Boolean variable named IsDead.

Next, go to the Event Tick node and create a Branch node.

Afterwards, get a reference to IsDead and connect it to the Condition pin of the Branch node.

Connect the Event Tick node to the Branch node. Afterwards, connect the False pin of the Branch node to the AddActorWorldOffset node.

Now, whenever IsDead is set to true, the player will stop moving forward.

Next, let’s set the IsDead variable when the player hits a wall.

Setting the IsDead Variable

Click Compile and then switch to BP_Tunnel. In the Components panel, right-click on WallMesh and select Add Event\Add OnComponentHit. This will add the following node to your Event Graph:

This node will execute whenever another Actor collides with WallMesh.

First, you need to check if the Actor that collided with WallMesh is the player.

Left-click and drag the Other Actor pin. Release left-click on an empty area and select Cast to BP_Player from the menu.

Next, left-click and drag the BP_Player pin of the Cast to BP_Player node. Release left-click on an empty space and then add a Set Is Dead node.

Set IsDead to true by left-clicking the checkbox.

Click Compile and then go back to the main editor. Press Play and try hitting a wall. If you move around to a hole, you will no longer move through it.

In the next section, you will display a restart button when the player hits a wall.

Displaying a Restart Button

The widget that you will display is named WBP_Restart. You can find it in the UI folder. This is what it looks like:

To display or hide the widget, you need a reference to it. Open BP_Player and then create a new variable named RestartWidget. Change the Variable Type to WBP_Restart\Object Reference.

Next, go to the Event Graph and locate the Event BeginPlay node.

Add a Create Widget node and set the Class value to WBP_Restart.

Afterwards, add a Set Restart Widget node and then connect everything like so:

Now, when the player spawns, it will create an instance of WBP_Restart. The next step is to make a function that displays this instance.

Creating the Display Function

Create a new function and name it DisplayRestart. Once you have done that, create the following graph:

Summary:

  1. Add to Viewport will show RestartWidget on the screen
  2. Set Input Mode UI Only will limit the player’s interactions to the UI. This is so the player can’t move around while they are dead.
  3. As its name suggets, Set Show Mouse Cursor simply displays the mouse cursor

To display the restart button, all you need to do is call DisplayRestart after the player collides with a wall.

Calling the Display Function

Close the DisplayRestart graph and then click Compile.

Switch to BP_Tunnel and then locate the On Component Hit (WallMesh) node.

Add a DisplayRestart node to the end of the node chain.

Click Compile and then close BP_Tunnel. Go back to the main editor and press Play. If you hit a wall, the restart button will appear.

The last step is to restart the game when the player clicks the button.

Restarting the Game

There are two things the game needs to do when restarting:

  1. Reset the player. This includes removing the restart button from the screen.
  2. Respawn the tunnels. This is so the player starts at the beginning of the tunnel.

Let’s start with resetting the player.

Resetting the Player

Open BP_Player and then create a new function called RestartGame. Create the following graph:

Summary:

  1. Set Is Dead sets IsDead to false. This re-enables forward movement.
  2. Remove From Parent removes RestartWidget from the screen
  3. Set Input Mode Game Only re-enables game input so the player can move around
  4. Set Show Mouse Cursor hides the mouse cursor

Next, let’s respawn the tunnels.

Respawning the Tunnels

Click Compile and then close BP_Player.

Open BP_TunnelSpawner and make sure you are in the SpawnInitialTunnels graph.

First, you need to remove the existing tunnels before you spawn new ones.

Add a Sequence node after the Entry node. Connect the Then 1 pin to the ForLoop node.

Note: The Sequence node executes its outputs in sequential order. It is a great way to organize your graph vertically especially since node chains can get very long.

Next, create the following nodes:

This setup will get all the existing tunnels and remove them from the game.

Finally, connect the Then 0 pin of the Sequence node to the Get All Actors of Class node. This will make sure the tunnels are removed before the spawning process.

Here is the final graph:

The last thing to do is handle the button click.

Handling Button Clicks

Click Compile and then close BP_TunnelSpawner.

Go to the Content Browser and navigate to the UI folder. Double-click on WBP_Restart to open it.

Select RestartButton and then go to the Details panel. Go to the Events section and click the button next to OnClicked.

This will create a node called On Clicked (RestartButton). This node will execute when the player clicks on RestartButton.

Recreate the following:

Summary:

  1. Get Owning Player Pawn returns the Pawn that the player is currently controlling
  2. Cast to BP_Player checks if the Pawn is of class BP_Player
  3. If it is, it will call the RestartGame function. This function resets the player and hides the restart button.
  4. Get All Actors of Class and Get returns BP_TunnelSpawner and then calls SpawnInitialTunnels. This function will remove existing tunnels and spawn new ones.
Note: You might be wondering why I didn’t use a reference variable for BP_TunnelSpawner. The main reason is because BP_Tunnel doesn’t have a relationship to WBP_Restart. For a simple game like this, it is easier to do the above method rather than figuring out where to store a reference variable.

Click Compile and then close the Blueprint editor. Press Play to test out the restart button!

Where to Go From Here?

You can download the completed project here.

Now that you have a simple game, you can start building on it. Try adding a score counter that increases when the player avoids a wall.

You should also try to build classic games like Pong and Tetris. The mechanics of these games are simple but can be a challenge to implement.

If you’d like to learn how to make a specific type of game, leave a comment below!

The post How to Create a Simple Game in Unreal Engine 4 appeared first on Ray Wenderlich.

Screencast: Introduction to ARKit: Error Management

RWDevCon 2018: Choose Your Topics in One Week!

$
0
0

RWDevCon-feature

Next April, we are running an iOS conference focused on high quality hands-on tutorials called RWDevCon 2018.

One of the unique things about RWDevCon is it’s coordinated as a team.

That means we can do some cool things, like let you decide the content of the conference. Here’s how it works:

  1. Send your suggestions. First we’ll send an email to everyone who’s bought a ticket, asking for ideas for tutorials. For example, you might suggest a tutorial on ARKit, CoreML, or Metal.
  2. Vote your favorites. We’ll put the most common suggestions on a survey, and you can vote on which you’d like in the conference.
  3. Enjoy your top picks. Based on the results, we’ll be sure to cover everyone’s top picks, and match speakers to topics based on experience. w00t!

There’s no other conference like this – RWDevCon is truly a conference where you decide what’s inside.

This process is starting next week, so if you’d like to be a part of the decision making process, grab your ticket now.

We can’t wait to see what you choose this year! :]

The post RWDevCon 2018: Choose Your Topics in One Week! appeared first on Ray Wenderlich.

Menus and Popovers in Menu Bar Apps for macOS

$
0
0

Update note: This Menus and Popovers in Menu Bar Apps for macOS tutorial has been updated to Xcode 9 and Swift 4 by Warren Burton. The original tutorial was written by Mikael Konutgan.

Learn how to make a menu bar macOS app with a popover!

Learn how to make a menu bar macOS app with a popover!

Menu bar apps have been staple of macOS for a long time. Many apps like 1Password and Day One have companion menu bar apps. Others like Fantastical live exclusively in macOS’s menu bar.

In this menu bar app tutorial, you’ll build a menu bar app that shows inspirational quotes in a popover. While you do this, you’ll learn:

  • How to create a menu bar icon
  • How to make the app live exclusively in the menu bar
  • How to add a menu for the user
  • How to make a popover that shows on demand and hides when the user moves on — aka Event Monitoring
Note: This tutorial assumes you’re familiar with Swift and macOS. If you need a refresher, start with our macOS Development for Beginners tutorial for a great introduction.

Getting Started

Fire up Xcode. Go to File/New/Project… then select the macOS/Application/Cocoa App template and click Next.

On the next screen, enter Quotes as the Product Name, choose your desired Organization Name and Organization Identifier. Then make sure that Swift is selected as the language, and that Use Storyboards is checked. Uncheck Create Document-Based Application, Use Core Data, Include Unit tests and Include UI Tests.

configure new project

Finally, click Next again, choose a place to save the project and click Create.

Once the new project is set up, open AppDelegate.swift and add the following property to the class:

let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)

This creates a Status Item — aka application icon — in the menu bar with a fixed length that the user will see and use.

Next, you’ll need to associate an image to the status item to make your app recognizable in the menu bar.

Go to Assets.xcassets in the project navigator, download this image StatusBarButtonImage@2x.png and drag it into the asset catalog.

Select the image and open the attributes inspector. Change the Render As option to Template Image.

If you use your own custom image, make sure that the image is black and white and configured as a template image so the Status Item looks great against both light and dark menu bars.

Back in AppDelegate.swift, add the following code to applicationDidFinishLaunching(_:)

if let button = statusItem.button {
  button.image = NSImage(named:NSImage.Name("StatusBarButtonImage"))
  button.action = #selector(printQuote(_:))
}

This will configure the status item with an icon of the image you just added, and an action for when you click on the item. This will create an error but you’ll fix that now.

Add the following method to the class:

@objc func printQuote(_ sender: Any?) {
  let quoteText = "Never put off until tomorrow what you can do the day after tomorrow."
  let quoteAuthor = "Mark Twain"

  print("\(quoteText) — \(quoteAuthor)")
}

This method will simply log out the quote text to the console.

Take note of the @objc directive in the signature. This exposes the method to the Objective-C runtime to allow the button to use it as an action.

Build and run the app, and you should see a new menu bar app available. You did it!

light mode menu bar

dark mode menu bar

Note: If you have too many menu bar apps, you might not be able to see your button. Switch to an app with fewer menus than Xcode (like Finder) and you should be able to see it.

Every time you click on the menu bar icon, you’ll see the quote printed out in the Xcode console.

Hiding the Dock Icon and Main Window

There are still two small things to do before you have a functional menu bar app.

  1. Disable the dock icon.
  2. Remove the main window.

To disable the dock icon, open Info.plist. Add a new key Application is agent (UIElement) and set its value to YES.

Note: If you’re an expert plist editor, feel free to set this manually with the key LSUIElement.

Now it’s time to handle the main window.

  • Open Main.storyboard
  • Select the Window Controller scene and delete it.
  • Leave the View Controller scene alone as you are going to use it soon.

delete window scene

Build and run. You’ll see the app has no main window, no pesky dock icon and only a tidy status item in the menu bar. High five yourself :]

Adding a Menu to the Status Item

Usually, a measly single action on click is not enough for a menu bar app. The easiest way to add more functionality to your app is to add a menu. Add the following function to the end of AppDelegate.

func constructMenu() {
  let menu = NSMenu()

  menu.addItem(NSMenuItem(title: "Print Quote", action: #selector(AppDelegate.printQuote(_:)), keyEquivalent: "P"))
  menu.addItem(NSMenuItem.separator())
  menu.addItem(NSMenuItem(title: "Quit Quotes", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))

  statusItem.menu = menu
}

and then add this call to the end of applicationDidFinishLaunching(_:)

constructMenu()

Here you create an NSMenu, add 3 instances of NSMenuItem to it, and then set the status item’s menu to that new menu.

A few things to note here:

  • The title of a menu item is the text that appears in the menu. This is a good point for localization if needed.
  • The action, like the action of a button or any control, is the method that gets called when you click the menu item.
  • The keyEquivalent is a keyboard shortcut that you can use to activate the menu item. A lowercase letter uses Cmd as the modifier key and an uppercase letter uses Cmd+Shift. This keyboard shortcut only works if the application is front-most and active. So, in this case, the menu or any other window needs to be visible, since the app has no dock icon.
  • A separatorItem is a stock inactive menu item that appears as a simple gray line between other menu items. Use it to group functionality in the menu.
  • The printQuote: action is the method you already defined in AppDelegate while terminate: is an action method defined by NSApplication.

Build and run, and you should see a menu when clicking on the status item. Progress!

status bar item menu

Try out your options – selecting Print Quote will display the quote in the Xcode console, while Quit Quotes will quit the app.

Adding a Popover to the Status Item

You’ve seen how easy it is to set up a menu from code, but showing the quote in the Xcode console won’t cut it for most of your end users. The next step is to replace the menu with a simple view controller to show a quote right in place.

Go to File/New/File…, select the macOS/Source/Cocoa Class template and click Next.

  • Name the class QuotesViewController.
  • Make it a subclass of NSViewController.
  • Ensure that Also create XIB file for user interface is not checked.
  • Set the language to Swift.

Finally, click Next again, choose a place to save the file (In the Quotes subfolder of the project folder is a good place) and click Create.

Now open Main.storyboard. Expand the View Controller Scene and select the View Controller instance.

First select the Identity Inspector and change the Class to QuotesViewController, next set the Storyboard ID to QuotesViewController

Next add the following code to the end of QuotesViewController.swift

extension QuotesViewController {
  // MARK: Storyboard instantiation
  static func freshController() -> QuotesViewController {
    //1.
    let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
    //2.
    let identifier = NSStoryboard.SceneIdentifier(rawValue: "QuotesViewController")
    //3.
    guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? QuotesViewController else {
      fatalError("Why cant i find QuotesViewController? - Check Main.storyboard")
    }
    return viewcontroller
  }
}

What happens here is…

  1. Get a reference to Main.storyboard.
  2. Create a Scene identifier that matches the one you set just before.
  3. Instantiate QuotesViewController and return it.

You create this method so that anything thats using QuotesViewController doesn’t need to know how to instantiate it. It just works :]

Notice the fatalError inside the guard statement. Its often good to use this or assertionFailure to let yourself or other team members know when you have messed up during development.

Now go back to AppDelegate.swift. Start by adding a new property declaration to the class:

let popover = NSPopover()

Next, replace applicationDidFinishLaunching(_:) with the following:

func applicationDidFinishLaunching(_ aNotification: Notification) {
  if let button = statusItem.button {
    button.image = NSImage(named:NSImage.Name("StatusBarButtonImage"))
    button.action = #selector(togglePopover(_:))
  }
  popover.contentViewController = QuotesViewController.freshController()
}

You’ve changed the button action to togglePopover(_:) which you’ll implement next. Also, rather than set up a menu, you’re setting up the popover to show whatever’s in QuotesViewController.

Add the following three methods to AppDelegate

@objc func togglePopover(_ sender: Any?) {
  if popover.isShown {
    closePopover(sender: sender)
  } else {
    showPopover(sender: sender)
  }
}

func showPopover(sender: Any?) {
  if let button = statusItem.button {
    popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
  }
}

func closePopover(sender: Any?) {
  popover.performClose(sender)
}

showPopover() displays the popover to the user. You just need to supply a source rect and macOS will position the popover and arrow so it looks like it’s coming out of the menu bar icon.

closePopover() simply closes the popover, and togglePopover() is the action method that will either open or close the popover depending on its current state.

Build and run, and then click on the menu bar icon to check that it shows and then hides an empty popover.

Your popover works great, but where’s all the inspiration? All you see is an empty view and no quotes. Guess what you’ll do next?

Implementing the Quote View Controller

First, you need a model to store the quotes and attributions. Go to File/New/File… and select the macOS/Source/Swift File template, then click Next. Name the file Quote and click Create.

Open Quote.swift and add the following code to the file:

struct Quote {
  let text: String
  let author: String

  static let all: [Quote] =  [
    Quote(text: "Never put off until tomorrow what you can do the day after tomorrow.", author: "Mark Twain"),
    Quote(text: "Efficiency is doing better what is already being done.", author: "Peter Drucker"),
    Quote(text: "To infinity and beyond!", author: "Buzz Lightyear"),
    Quote(text: "May the Force be with you.", author: "Han Solo"),
    Quote(text: "Simplicity is the ultimate sophistication", author: "Leonardo da Vinci"),
    Quote(text: "It’s not just what it looks like and feels like. Design is how it works.", author: "Steve Jobs")
  ]
}

extension Quote: CustomStringConvertible {
  var description: String {
    return "\"\(text)\" — \(author)"
  }
}

This defines a simple quote structure and a static property that returns all the quotes. Since you also make Quote conform to CustomStringConvertible, you can easily get a nice formatted string.

You’re making progress, but you now need some function in the UI to display all these famous quotes.

Setting up the View Controller UI

Open Main.storyboard and drag 3 Push Button instances, and a Multiline Label into the view controller.

Drag the buttons and label into place until they look like this layout. The dotted blue layout guides that appear will help you get the items into place:

view controller layout

Can you add the auto layout constraints to make the user interface match? Give it a few good attempts before you open the spoiler below. If you get it right, skip the spoiler and give yourself a gold star.

Solution Inside SelectShow>

Once you have the layout setup to your satisfaction set up the elements like this:

  • Set the left button’s image to NSGoLeftTemplate and delete the title.
  • Set the right button’s image to NSGoRightTemplate and delete the title.
  • Set the title of the lower push button to Quit Quotes.
  • Set the label’s text alignment to center.
  • Check that Line Break for the label is set to Word Wrap.

Now open QuotesViewController.swift and add the the following code to the class implementation of QuotesViewController:

@IBOutlet var textLabel: NSTextField!

Add this extension after the class implementation. You will now have two extensions in QuotesViewController.swift.

// MARK: Actions

extension QuotesViewController {
  @IBAction func previous(_ sender: NSButton) {
  }

  @IBAction func next(_ sender: NSButton) {
  }

  @IBAction func quit(_ sender: NSButton) {
  }
}

You have just added an outlet for the text label, which you’ll use to display the inspirational quote and 3 stub actions which you will connect to the 3 buttons.

Connect code to Interface Builder

You’ll notice that Xcode has placed circles in the left hand margin of your source editor. The circles are handles that appear when you use the @IBAction and @IBOutlet keywords.

You will now use them to connect your code to the UI.

While holding down alt click on Main.storyboard in the project navigator. This should open the storyboard in the Assistant Editor on the right and the source on the left.

Drag from the circle next to textLabel to the label in interface builder. In the same way connect the previous, next and quit actions to the left, right and bottom buttons respectively.

Note: If you have trouble with any of the above steps, refer to our library of macOS tutorials, where you’ll find introductory tutorials that will walk you through many aspects of macOS development, including adding views/constraints in interface builder and connecting outlets and actions.

Stand up, stretch and maybe do a quick victory lap around your desk because you just flew through a bunch of interface builder work.

Build and run, and your popover should look like this now:

You used the default size of the view controller for the popover above. If you want a smaller or bigger popover, all you need to do is resize the view controller in the storyboard.

The interface is finished, but you’re not done yet. Those buttons are waiting on you to know what to do when the user clicks them — don’t leave them hanging.

Create actions for the buttons

If you haven’t already dismiss the Assistant Editor with Cmd-Return or View > Standard Editor > Show Standard Editor

Open QuotesViewController.swift and add the following properties to the class implementation:

let quotes = Quote.all

var currentQuoteIndex: Int = 0 {
  didSet {
    updateQuote()
  }
}

The quotes property holds all the quotes, and currentQuoteIndex holds the index of the current quote displayed. currentQuoteIndex also has a property observer to update the text label string with the new quote when the index changes.

Next, add the following methods to the class:

override func viewDidLoad() {
  super.viewDidLoad()
  currentQuoteIndex = 0
}

func updateQuote() {
  textLabel.stringValue = String(describing: quotes[currentQuoteIndex])
}

When the view loads, you set the current quote index to 0, which in turn updates the user interface. updateQuote() simply updates the text label to show whichever quote is currently selected according to currentQuoteIndex.

To tie it all together, update the three action methods as follows;

@IBAction func previous(_ sender: NSButton) {
  currentQuoteIndex = (currentQuoteIndex - 1 + quotes.count) % quotes.count
}

@IBAction func next(_ sender: NSButton) {
  currentQuoteIndex = (currentQuoteIndex + 1) % quotes.count
}

@IBAction func quit(_ sender: NSButton) {
  NSApplication.shared.terminate(sender)
}

In next() and previous(), you cycle through the all the quotes and wrap around when you reach the ends of the array. quit terminates the app.

Build and run again, and now you can cycle back and forward through the quotes and quit the app!

final UI

Event Monitoring

There is one feature your users will want in your unobtrusive, small menu bar app, and that’s when you click anywhere outside the app, the popover automatically closes.

Menu bar apps should open the UI on click, and then disappear once the user moves onto the next thing. For that, you need an macOS global event monitor.

Next you’ll make an event monitor thats reusable in all your projects and then use it when showing the popover.

Bet you’re feeling smarter already!

Create a new Swift File and name it EventMonitor, and then replace its contents with the following class definition:

import Cocoa

public class EventMonitor {
  private var monitor: Any?
  private let mask: NSEvent.EventTypeMask
  private let handler: (NSEvent?) -> Void

  public init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> Void) {
    self.mask = mask
    self.handler = handler
  }

  deinit {
    stop()
  }

  public func start() {
    monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler)
  }

  public func stop() {
    if monitor != nil {
      NSEvent.removeMonitor(monitor!)
      monitor = nil
    }
  }
}

You initialize an instance of this class by passing in a mask of events to listen for – things like key down, scroll wheel moved, left mouse button click, etc – and an event handler.

When you’re ready to start listening, start() calls addGlobalMonitorForEventsMatchingMask(_:handler:), which returns an object for you to hold on to. Any time the event specified in the mask occurs, the system calls your handler.

To remove the global event monitor, you call removeMonitor() in stop() and delete the returned object by setting it to nil.

All that’s left is calling start() and stop() when needed. How easy is that? The class also calls stop() for you in the deinitializer, to clean up after itself.

Connect the Event Monitor

Open AppDelegate.swift one last time, and add a new property declaration to the class:

var eventMonitor: EventMonitor?

Next, add the code to configure the event monitor at the end of applicationDidFinishLaunching(_:)

eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in
  if let strongSelf = self, strongSelf.popover.isShown {
    strongSelf.closePopover(sender: event)
  }
}

This notifies your app of any left or right mouse down event and closes the popover when the system event occurs. Note that your handler will not be called for events that are sent to your own application. That’s why the popover doesn’t close when you click around inside of it. :]

You use a weak reference to self to avoid a potential retain cycle between AppDelegate and EventMonitor. It’s not essential in this particular situation because there’s only one setup cycle but is something to watch out for in your own code when you use block handlers between objects.

Add the following code to the end of showPopover(_:):

eventMonitor?.start()

This will start the event monitor when the popover appears.

Then, you’ll need to add the following code to the end of closePopover(_:):

eventMonitor?.stop()

This will stop the event monitor when the popover closes.

All done! Build and run the app one more time. Click on the menu bar icon to show the popover, and then click anywhere else and the popover closes. Awesome!

Where To Go From Here?

Here is the final project with all of the code you’ve developed in the above tutorial.

You’ve seen how to set up both menus and popovers in your menu bar status items – why not keep experimenting with using attributed text or multiple labels to style the quote, or connecting to a web backend to pull new quotes. Maybe you can discover how to use the keyboard to cycle through the quotes.

A good place to look for other possibilities is reading the official documentation for NSMenu, NSPopover and NSStatusItem.

One thing to consider is that you are asking your customers for a very privileged piece of screen real estate so while you might think its cool to have a status bar item your users might not feel the same way. A lot of apps manage this by providing preferences to show or hide the item. You can use that as an advanced exercise for yourself.

Thanks for taking the time to learn how to make a cool popover menu app for macOS. For now, it’s pretty simple, but you can see that the concepts you’ve learned here are an excellent foundation for a variety of apps.

If you have any questions, awesome discoveries or ideas you’d like to bounce off others as you configure status items, menus or popovers in your apps, please let me know in the forum discussion below! :]

The post Menus and Popovers in Menu Bar Apps for macOS appeared first on Ray Wenderlich.

How To Implement A Circular Image Loader Animation with CAShapeLayer

$
0
0

Update note:: This tutorial has been updated for Xcode 9, iOS 11 and Swift 4 by Michael Katz. The original tutorial was written by Rounak Jain.

CAShapeLayer tutorial

A long while ago, Michael Villar created a really interesting loading animation for his post on Motion Experiments.

The GIF to the right shows the loading animation, which marries a circular progress indicator with a circular reveal animation. The combined effect is fascinating, unique, and more than a little mesmerizing! :]

This CAShapeLayer tutorial will show you how to recreate this exact effect in Swift and Core Animation. Let’s get animating!

Getting Started

First download the starter project for this CAShapeLayer tutorial.

Take a minute and browse through the project once you’ve extracted it. There’s a ViewController that has a UIImageView subclass named CustomImageView, along with a SDWebImage method call to load the image. The starter project already has the views and image loading logic in place.

Build and run. After a moment, you should see a simple image displayed as follows:

CAShapeLayer tutorial

You might notice when you first run the app, the app seems to pause for a few seconds while the image downloads, then the image appears on the screen without fanfare. Of course, there’s no circular progress indicator at the moment – that’s what you’ll create in this CAShapeLayer tutorial!

You’ll create this animation in two distinct phases:

  1. Circular progress. First, you’ll draw a circular progress indicator and update it based on the progress of the download.
  2. Expanding circular image. Second, you’ll reveal the downloaded image through an expanding circular window.

Follow along closely to prevent yourself from going “round in circles”! :]

Creating the Circular Indicator

Think for a moment about the basic design of the progress indicator. The indicator is initially empty to show a progress of 0%, then gradually fills in as the image is downloaded. This is fairly simple to achieve with a CAShapeLayer whose path is a circle.

Note: If you’re new to the concept of CAShapeLayer (or CALayers in general, check out Scott Gardner’s CALayer in iOS with Swift article.

You can control the start and end position of the outline, or stroke, of your shape with the CAShapeLayer properties strokeStart and strokeEnd. By varying strokeEnd between 0 and 1, you can fill in the stroke appropriately to show the progress of the download.

Let’s try this out. Create a new file with the iOS\Source\Cocoa Touch Class template. Name it CircularLoaderView and set subclass of to UIView as shown below:

CAShapeLayer tutorial

Click Next, and then Create. This new subclass of UIView will house all of your new animation code.

Open CircularLoaderView.swift and add the following properties to the top of the class:

let circlePathLayer = CAShapeLayer()
let circleRadius: CGFloat = 20.0

circlePathLayer represents the circular path, while circleRadius will be the radius of the circular path. Rocket science! I know.

Next, add the following initialization code right below circleRadius to configure the shape layer:

override init(frame: CGRect) {
  super.init(frame: frame)
  configure()
}

required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  configure()
}

func configure() {
  circlePathLayer.frame = bounds
  circlePathLayer.lineWidth = 2
  circlePathLayer.fillColor = UIColor.clear.cgColor
  circlePathLayer.strokeColor = UIColor.red.cgColor
  layer.addSublayer(circlePathLayer)
  backgroundColor = .white
}

Both of the initializers call configure(). configure() sets up circlePathLayer to have a frame that matches the view’s bounds, a line width of 2 points, a clear fill color and a red stroke color. Next, it adds the shape layer as a sublayer of the view’s own layer and sets the view’s backgroundColor to white so the rest of the screen is blanked out while the image loads.

Adding the Path

Now you’ve configured the layer, it’s time to set its path. Start by adding the following helper method right below configure():

func circleFrame() -> CGRect {
  var circleFrame = CGRect(x: 0, y: 0, width: 2 * circleRadius, height: 2 * circleRadius)
  let circlePathBounds = circlePathLayer.bounds
  circleFrame.origin.x = circlePathBounds.midX - circleFrame.midX
  circleFrame.origin.y = circlePathBounds.midY - circleFrame.midY
  return circleFrame
}

In this simple method you calculate the CGRect to contain the indicator’s path. You set the bounding rectangle to have a width and a height equals to 2 * circleRadius and position it at the center of the view. The reason why you wrote a separate method to handle this simple operation is you’ll need to recalculate circleFrame each time the view’s size changes.

Next, add the following method below circleFrame() to create your path:

func circlePath() -> UIBezierPath {
  return UIBezierPath(ovalIn: circleFrame())
}

This simply returns the circular UIBezierPath as bounded by circleFrame(). Since circleFrame() returns a square, the “oval” in this case will end up as a circle.

Since layers don’t have an autoresizingMask property, you’ll override layoutSubviews to respond appropriately to changes in the view’s size.

Override layoutSubviews() by adding the following code:

override func layoutSubviews() {
  super.layoutSubviews()
  circlePathLayer.frame = bounds
  circlePathLayer.path = circlePath().cgPath
}

You’re calling circlePath() here because a change in the frame should also trigger a recalculation of the path.

Open CustomImageView.swift. Add the following property to the top of the class:

let progressIndicatorView = CircularLoaderView(frame: .zero)

This property is an instance of the CircularLoaderView class you just created.

Next, add the following to init(coder:), right before let url...:

addSubview(progressIndicatorView)

addConstraints(NSLayoutConstraint.constraints(
  withVisualFormat: "V:|[v]|", options: .init(rawValue: 0),
  metrics: nil, views: ["v": progressIndicatorView]))
addConstraints(NSLayoutConstraint.constraints(
  withVisualFormat: "H:|[v]|", options: .init(rawValue: 0),
  metrics: nil, views:  ["v": progressIndicatorView]))
progressIndicatorView.translatesAutoresizingMaskIntoConstraints = false

Here you add the progress indicator view as a subview of the custom image view. Then you add two layout constraints to ensure the progress indicator view remains the same size as the image view. Finally, you set translatesAutoresizingMaskIntoConstraints to false so the autoresizing mask doesn’t interfere with the Auto Layout engine.

Build and run your project; you should see a red, hollow circle appear like so:

CAShapeLayer tutorial

Awesome! Your progress indicator is showing on the screen.

Modifying the Stroke Length

Open CircularLoaderView.swift and add the following lines directly below the other properties in the file:

var progress: CGFloat {
  get {
    return circlePathLayer.strokeEnd
  }
  set {
    if newValue > 1 {
      circlePathLayer.strokeEnd = 1
    } else if newValue < 0 {
      circlePathLayer.strokeEnd = 0
    } else {
      circlePathLayer.strokeEnd = newValue
    }
  }
}

Here you create a computed property — that is, a property without any backing variable — that has a custom setter and getter. The getter simply returns circlePathLayer.strokeEnd, and the setter validates the input is between 0 and 1 and sets the layer’s strokeEnd property accordingly.

Add the following line at the top of configure() to initialize progress on first run:

progress = 0

Build and run your project; you should see nothing but a blank white screen. Trust me! This is good news! :] Setting progress to 0 in turn sets the strokeEnd to 0, which means no part of the shape layer was drawn.

CAShapeLayer tutorial

The only thing left to do with your indicator is to update progress in the image download callback.

Open CustomImageView.swift and replace the comment Update progress here with the following:

self?.progressIndicatorView.progress = CGFloat(receivedSize) / CGFloat(expectedSize)

Here you calculate the progress by dividing receivedSize by expectedSize.

Note: You'll notice the block uses a weak reference to self - this is to avoid a retain cycle.

Build and run your project. You'll see the progress indicator begin to move like so:

CAShapeLayer tutorial

Even though you didn't add any animation code yourself, CALayer handily detects any animatable property on the layer and smoothly animates it as it changes. Neat!

That takes care of the first phase. Now on to the second and final phase — the big reveal! :]

Creating the Reveal Animation

The reveal phase gradually displays the image in a window in the shape of an expanding circular ring. If you’ve read this tutorial on creating a Ping-style view controller animation, you'll know this is a perfect use-case of the mask property of a CALayer.

Open CircularLoaderView.swift and add the following method:

func reveal() {
  // 1
  backgroundColor = .clear
  progress = 1
  // 2
  circlePathLayer.removeAnimation(forKey: "strokeEnd")
  // 3
  circlePathLayer.removeFromSuperlayer()
  superview?.layer.mask = circlePathLayer
}

This is an important method to understand, so let's go over this section by section:

  1. You clear the view’s background color so the image behind the view isn’t hidden anymore, and you set progress to 1.
  2. You remove any pending implicit animations for the strokeEnd property, which may have otherwise interfered with the reveal animation. For more about implicit animations, check out iOS Animations by Tutorials.
  3. You remove circlePathLayer from its superLayer and assign it instead to the superView’s layer mask, so the image is visible through the circular mask "hole". This lets you reuse the existing layer and avoid duplicating code.

Now you need to call reveal() from somewhere. Replace the Reveal image here comment in CustomImageView.swift with the following:

if let error = error {
  print(error)
}
self?.progressIndicatorView.reveal()

Build and run. Once the image downloads you'll see it partially revealed through a small ring:

CAShapeLayer tutorial

You can see your image in the background — but just barely! :]

Expanding Rings

Your next step is to expand this ring both inwards and outwards. You could do this with two separate, concentric UIBezierPath, but you can do it in a more efficient manner with just a single Bezier path.

How? You simply increase the circle’s radius to expand outward by changing the path property, while simultaneously increasing the line's width to make the ring thicker and expand inward by changing the lineWidth property. Eventually, both values grow enough to reveal the entire image underneath.

Open CircularLoaderView.swift and add the following code to the end of reveal():

// 1
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let finalRadius = sqrt((center.x*center.x) + (center.y*center.y))
let radiusInset = finalRadius - circleRadius
let outerRect = circleFrame().insetBy(dx: -radiusInset, dy: -radiusInset)
let toPath = UIBezierPath(ovalIn: outerRect).cgPath

// 2
let fromPath = circlePathLayer.path
let fromLineWidth = circlePathLayer.lineWidth

// 3
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
circlePathLayer.lineWidth = 2*finalRadius
circlePathLayer.path = toPath
CATransaction.commit()

// 4
let lineWidthAnimation = CABasicAnimation(keyPath: "lineWidth")
lineWidthAnimation.fromValue = fromLineWidth
lineWidthAnimation.toValue = 2*finalRadius
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.fromValue = fromPath
pathAnimation.toValue = toPath

// 5
let groupAnimation = CAAnimationGroup()
groupAnimation.duration = 1
groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
groupAnimation.animations = [pathAnimation, lineWidthAnimation]
circlePathLayer.add(groupAnimation, forKey: "strokeWidth")

This might look like a lot of code, but what you're doing here is fairly simple:

  1. You determine the radius of the circle that can fully circumscribe the image view and use it to calculate the CGRect that would fully bound this circle. toPath represents the final shape of the CAShapeLayer mask like so:
    CAShapeLayer tutorial
  2. You set the initial values of lineWidth and path to match the current values of the layer.
  3. You set lineWidth and path to their final values. This prevents them from jumping back to their original values when the animation completes. By wrapping this changes in a CATransaction with kCATransactionDisableActions set to true you disable the layer’s implicit animations.
  4. You create two instances of CABasicAnimation: one for path and the other for lineWidth. lineWidth has to increase twice as fast as the radius increases in order for the circle to expand inward as well as outward.
  5. You add both animations to a CAAnimationGroup, and add the animation group to the layer.

Build and run your project. You’ll see the reveal animation kick-off once the image finishes downloading:

CAShapeLayer tutorial

Notice a portion of the circle remains on the screen once the reveal animation is done. To fix this, add the following extension to the end of CircularLoaderView.swift implementing animationDidStop(_:finished:):

extension CircularLoaderView: CAAnimationDelegate {
  func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
    superview?.layer.mask = nil
  }
}

This code removes the mask on the super layer, which removes the circle entirely.

Finally, at the bottom of reveal(), just above the line circlePathLayer.add(groupAnimation, forKey: "strokeWidth") add the following line:

groupAnimation.delegate = self

This assigns the delegate so the animationDidStop(_:finished:) gets called.

Build and run your project. Now you’ll see the full effect of your animation:

CAShapeLayer tutorial

Congratulations, you've finished creating the circular image loading animation!

Where to Go From Here?

You can download the completed project here.

From here, you can further tweak the timing, curves and colors of the animation to suit your needs and personal design aesthetic. One possible improvement is to use kCALineCapRound for the shape layer's lineCap property to round off the ends of the circular progress indicator. See what improvements you can come up with on your own!

If you enjoyed this CAShapeLayer tutorial and would like to learn how to create more animations like these, check out Marin Todorov's book iOS Animations by Tutorials, which starts with basic view animations and moves all the way to layer animations, animating constraints, view controller transitions, and more.

If you have any questions or comments about the CAShapeLayer tutorial, please join the discussion below. I'd also love to see ways in which you've incorporated this cool animation in your app!

The post How To Implement A Circular Image Loader Animation with CAShapeLayer appeared first on Ray Wenderlich.

iOS 11 by Tutorials: First 7 Chapters Now Available!

$
0
0

Great news everyone: The second early access release of iOS 11 by Tutorials is now available!

This release has seven chapters:

  • Chapter 6: Beginning Drag and Drop: Take advantage of the new drag-and-drop features to select one or more files from within your application, drag them around, and drop them on target locations within your app. In this chapter you’ll dive straight in and get your hands dirty with the latter set of APIs, by integrating drag and drop into an iPad bug-tracking app known as Bugray.
  • Chapter 7: Advanced Drag and Drop: In Beginning Drag and Drop, you learned the basics while focusing on collection views. In this chapter you’ll learn how to drag and drop between apps. You’ll also dive deeper, and learn some flexible APIs for working with custom views.
  • Chapter 9: Core ML & Vision Framework: Vision provides several out-of-box features for analyzing images and video. Its supported features include face tracking, face detection, face landmarks, text detection, rectangle detection, barcode detection, object tracking, and image registration. In this chapter, you’ll learn to detect faces, work with facial landmarks, and classify scenes using Vision and Core ML.
  • Chapter 10: Natural Language Processing: Learn how to detect the language of a body of text, how to work with named entities, how sentiment analysis works, how to perform searches with NSLinguisticTagger, and more! In this chapter, you’ll build an app that analyzes movie reviews for salient information. It will identify the review’s language, any actors mentioned, calculate sentiment (whether the reviewer liked, or hated, the movie), and provide text-based search.
  • Chapter 11: Introduction to ARKit: Build your own augmented reality app as you learn how to set up ARKit, detect feature points and draw planes, how to create and locate 3D models in your scene, handle low lighting conditions and manage session interruptions. With all the amazing things happening in AR lately, you won’t want to miss this chapter!
  • Chapter 12: PDFKit: Finally — you can easily create and annotate PDFs using native Apple libraries on the iPhone with PDFKit. Learn how to create thumbnails, add text, UI controls and watermarks to your documents, and even create custom actions for the UI controls in your PDF documents.
  • Chapter 13: MusicKit: You’re getting a two-for-one deal in this chapter! The sample app for this chapter is a iMessage app with live views, which is new to iOS 11. You’ll build this into a working iMessage application that lets you send guess-that-song music quizzes back and forth with your friends, using your Apple Music library!

This is the second early access release for the book – stay tuned for another early access release soon!

Where to Go From Here?

Here’s how you can get your early access copy of iOS 11 by Tutorials:

  • If you’ve pre-ordered iOS 11 by Tutorials, you can log in to the store and download the early access edition of iOS 11 by Tutorials here.
  • If you haven’t yet pre-ordered iOS 11 by Tutorials

    , we’re offering a limited-time, pre-order sale price of $44.99.

    When you pre-order the book, you’ll get exclusive access to the upcoming early access releases of the book so you can get a jumpstart on learning all the new APIs. The full edition of the book will be released in Fall 2017.

Gone are the days when every third-party developer knew everything there is to know about iOS. The sheer size of iOS can make new releases seem daunting. That’s why the Tutorial Team has been working really hard to extract the important parts of the new APIs, and to present this information in an easy-to-understand tutorial format. This means you can focus on what you want to be doing — building amazing apps!

What are you most looking forward to learning about in iOS 11? Respond in the comments below and let us know!

The post iOS 11 by Tutorials: First 7 Chapters Now Available! appeared first on Ray Wenderlich.


Reader’s App Reviews – August 2017

$
0
0

While many of you spent your week at 360iDev to expand your programming knowledge, I’ve been enjoying our reader’s apps for this month!

I’m honored you are taking a quick break to see what the community has been building. Every month, readers like you release great apps built with a little help from our tutorials, books, and videos. I want to highlight a few today to give just a peek at what our fantastic readers are creating.

This month we have:

  • A social media app
  • An alternative locations map
  • A fun historical game
  • And of course, much more!

Keep reading to see the latest apps released by raywenderlich.com readers just like you.

Pingtumi


If you’re the type of person who always loves to know what your friends are doing, while also making sure that they know what you’re up to, then Pingtumi is the app for you.

Pingtumi is a notification/social media service that allows you to follow your friends, as well as other people and be notified of where they are and what they are doing through the posts that they make.

The way these posts work is similar to a subscription feed. First, you create a feed around whatever theme you want by giving it a title and picture. Second, you invite people to subscribe to your feed.

Finally, you add posts to your feed that can contain your location, a title, a picture, and a message. All of your subscribers will be notified about your post and be able to leave comments on it.

It’s a very simple and focused alternative to other social media outlets, and I suggest that you give it a try and leave a review!

 

Roastafarian


This app is for all you coffee addicts out there! Roastafarian is an app that helps you keep track of your favorite brews.

You are able to keep a journal including the coffee’s name, price, retailer, roaster, your personal rating of it, and any additional notes you might have about its flavor or preparation.

Then, when you aren’t quite sure of what you thought about that weird coffee from a few months ago, you can easily search through your list of coffee brews by using keywords, ratings, origin, or most recent. You’ll always know exactly what to order or how to make your favorite coffee brews.

If you’re a coffee nut, you should definitely give Roastafarian a download to help feed your coffee cravings.

 

Near Vicinity


Sometimes map apps can be a bit hard to deal with, by filtering certain locations based on its complex algorithms and searching within its own, unchangeable radius. Near Vicinity eliminates those problems by allowing you to search for keywords in location titles and determining your own search radius.

Just search for location titles or tags in the search bar. Then, pick your search radius, which can be anywhere from 50 meters to 10,000 meters.

Near Vicinity will show you all of the locations that meet your search criteria and provide them for you as map pins, or in a list. You can tap on them for more information about the location and also favorite that location so you can find it easily the next time.

Near Vicinity is a great way for you to keep track of your favorite locations, or just search nearby to see if your favorite restaurant exists while you’re visiting a new place.

 

aioCalculator


Let’s be honest, the default calculator app is somewhat limited in its capabilities. The aioCalculator (all in one Calculator) is for those of us who need to do more than what the basic calculator app allows.

The aioCalculator app really is all inclusive as the name suggests. It includes multitasking, a unit converter, a history page that allows you to see previous calculations, undo and redo capabilities, and many other features.

It even allows you to share the results of your calculations with your contacts so that they can stay up to speed with how much you’re getting done, thanks to your fancy new calculator app.

If you are in need of a calculator that allows you to perform strenuous calculations, as well as reviewing previous calculations and sharing them with your contacts, the aioCalculator is here to help you out. Download it for free and without ads!

 

Eventology


Everyone loves a good bit of trivia, especially whenever it’s in the form of a competitive game. Eventology allows you to pick whether or not certain events happened before or after a given date and then gives you points based on how many you got correct in that round.

You can play solo to test your history knowledge from a range of different historical periods or be more focused and complete every challenge in the “Journey Through Time” tab. This challenge mode takes you through different eras of time from The Big Bang to Stardate 41254.7.

Then, once you feel confident, challenge your friends or anyone else online to show them that you are the true history buff and that your knowledge cannot be surpassed!

I’ve had a great time playing and learning with Eventology and I bet you will too!

 

Rapid Currency Converter World Exchange Rates List


If you travel internationally a lot and would like to keep track of different countries exchange rates, as the name suggests, Rapid Currency Converter will help you to do just that.

You pick your home country and then pick from a list of many other countries to compare your currency rates with theirs. You will easily be able to see what your buying capacity in that country will be before you get there and it is all stored in one place. The app can also be used offline, though it may be a bit inaccurate depending on when you were connected to the internet last.

The Rapid Currency Converter World Exchange Rates List app is the perfect companion for anyone that does a great deal of international travel, stock market trading, or just has an interest in world economics. Try it out, it’s a lot easier than sifting through google search results.

 

Fialy


If you want to be able to track your monthly income and daily expenses without all of the hassle of inputting every bit of personal information, then Fialy is the perfect financial assistant for you.

Fialy helps you to keep track of your income and expenses in a simple and intuitive way. You just tell it when and how much you received from your paycheck each time you get paid.

Then, whenever you make a purchase, just log it in Fialy and it will tell you your remaining budget for the month. It is much less complicated than the other financial assistant apps out there and is great for those people that like to manually input their budget data.

If you’re looking to get into budgeting in a simple way, then use Fialy for a bit and see what you think.

 

 

Where To Go From Here?

Each month, I really enjoy seeing what our community of readers comes up with. The apps you build are the reason we keep writing tutorials. Make sure you tell me about your next one, submit here.

If you saw an app you liked, hop to the App Store and leave a review! A good review always makes a dev’s day. And make sure you tell them you’re from raywenderlich.com; this is a community of makers.

If you’ve never made an app, this is the month! Check out our free tutorials to become an iOS star. What are you waiting for – I want to see your app next month.

The post Reader’s App Reviews – August 2017 appeared first on Ray Wenderlich.

Video Tutorial: Beginning Firebase Part 1: Introduction

Video Tutorial: Beginning Firebase Part 2: Installing Firebase

Video Tutorial: Beginning Firebase Part 3: Configuring Firebase

New Course: Beginning Firebase

$
0
0

We’ve recently given you the chance to earn your #GitWarrior badge with our Beginning Git course, and complete your Git training with Mastering Git. Today, we’re continuing our coverage of cross-platform tools with another new course: Beginning Firebase.

The Firebase database is a network-centric database run by Google that allows you to develop realtime-centric applications, such as chat applications, that can connect with hundreds of simultaneous users.

In this course, you’ll start from the beginning, learning how to set up and connect to Firebase, and how to save and retrieve data. By the end of the course, you’ll know how to implement a user management system. You’ll learn about user authentication, how to register users, and log users in an out, and more!

Let’s take a look at what’s inside:

Section 1: CRUD Operations

Introduction - Getting Started

Video 1: Introduction (Free!)
In this introductory video, you’ll learn about Firebase, why using it is a good idea, and what the course will cover.

Video 2: Installing Firebase
In this video, you’ll learn how to install Firebase into your app by way of the dependency management framework, CocoaPods.

Video 3: Configuring Firebase
Installing Firebase is not enough. You actually have to configure it in order to work with your app. In this video, you’ll learn how to do this.

Configuring Firebase

Video 4: JSON Challenge
In your first challenge of the course, you will design the structure of the JSON that you will use in this course.

JSON Challenge

Video 5: References (Free!)
References are what you use to save and read data to Firebase. In this video, you’ll learn how to use references in your Swift app.

Video 6: Saving Data
Saving data in Firebase is handled by references. In this video, you’ll set values to references and save them to Firebase.

Saving Data

Video 7: Reading Data
Saving data is one part of the equation. You also have to read it back. This video will show you how to do that with Firebase

Video 8: Reading Data Challenge
In your next challenge, you’ll read some data from Firebase. I’ll get you started and you’ll finish the rest.

Video 9: Updating and Deleting
Nothing lasts forever. Especially data. In this video, you’ll learn how to update data, but also, how to delete it.

Video 10: Deleting Data Challenge
In your third and final challenge of this section, you’ll delete some data using an alternative method briefly discussed in a previous video.

Video 11: Querying Data
You’ll often times need to get specific information from the database. This video will introduce you to query methods.

Video 12: Section 1 Conclusion
This video concludes the first section on Firebase. It reviews what was covered, and what is coming up next.

Section 2: Managing Users

User Accounts

Video 1: Introduction – User Accounts
This video will introduce you to Firebase’s registration system which you’ll use to register and log-in users.

Video 2: User Authentication
Firebase has many different authentication methods available to you. In this video, you’ll learn how to setup email authentication.

User Authentication

Video 3: Keychain Sharing
Working with Firebase, you’ll be required to enable keychain sharing. Curious about the keychain? Watch on to find out about it.

Video 4: User Creation
When making a user account system, users will need to create accounts. This video will walk you through the process.

User Creation

Video 5: Error Handling Challenge
When working user registration systems, you’ll encounter lots of errors. This challenge will introduce to the process of handling them.

Error Handling Challenge

Video 6: User Login
Once you have user accounts created, you have to login those users. This video will cover the process of handling user login.

Video 7: User Login Challenge
When creating a new user account, users will expect to be automatically logged into the system. Your challenge is to make it happen.

Video 8: Online Users
Firebase has the ability to allow you to display online user information. This isn’t an API. It’s just a clever use of references.

Video 9: Online User Count Challenge
You’ve learned how to get a list of currently online users. Your final challenge in this course is to display a user count to the end user.

Video 10: Course Conclusion
In your third and final challenge of this section, you’ll delete some data using an alternative method briefly discussed in a previous video.

Where To Go From Here?

Want to check out the course? You can watch two of the videos for free:

The rest of the course is for raywenderlich.com subscribers only. Here’s how you can get access:

  • If you are a raywenderlich.com subscriber: You can access the first three parts of Beginning Firebase. today, and the rest will be coming out over the next two weeks.
  • If you are not a subscriber yet: What are you waiting for? Subscribe now to get access to our new Mastering Git course and our entire catalog of over 500 videos.

There’s much more in store for raywenderlich.com subscribers – if you’re curious, you can check out our full schedule of upcoming courses.

I hope you enjoy our new course, and stay tuned for many more new courses and updates to come! :]

The post New Course: Beginning Firebase appeared first on Ray Wenderlich.

Auto Layout Tutorial in iOS 11: Getting Started

$
0
0
auto layout tutorial

Start thinking in auto layout constraints!

Update note: This tutorial has been updated to iOS 11, Xcode 9, and Swift 4 by Ryan Ackermann. The original tutorial was written by Matthijs Hollemans.

Have you ever been frustrated trying to make your apps look good in both portrait and landscape orientation? Is making screen layouts that support both the iPhone and iPad driving you to the brink of madness?

Despair no longer, I bring you good news!

Not only does Auto Layout make it easy to support different screen sizes in your apps, as a bonus it also makes internationalization almost trivial. You no longer have to make new nibs or storyboards for every language that you wish to support, and this includes right-to-left languages such as Hebrew or Arabic.

In this Auto Layout tutorial, you’ll learn all about constraints and how to apply them!

Auto what now?

If you’re relatively new to iOS development, the phrase “Auto Layout” may be completely foreign to you. So what is it exactly? Well, in the beginning, Apple made only one screen size for the iPhone. And it was good. Developers had an easy time creating interfaces that didn’t have to be terribly flexible, as they really only had to fit that one size. Fast forward to today, and you’re in a much different world: different sized iPhones, iPads, and more emphasis on landscape mode all demand user interfaces of different sizes. Rather than expect developers to create different interfaces for each screen size, Apple came up with a system for allowing elements within a user interface to grow, shrink, and move depending on the size of the screen.. and they called it Auto Layout.

Auto Layout is a system of constraints, or UI based rules, that govern the size and position of elements on the screen. While that may sound simple on the surface, as you’ll see in the tutorial Auto Layout can get quite complex very quickly! Before you dive in, it will be helpful to orient yourself with all the tools in Xcode and Interface Builder.

Auto Layout orientation

Before you start creating a simple user interface, you should take a tour of Xcode and Interface Builder. Interface Builder is a graphical tool inside Xcode that allows developers like you to create user interfaces using Auto Layout. Here’s some of the common terminology you will come across when using Interface Builder.

  • The Pin menu is located on the bottom right of the editor. This menu is used to add new constraints to UI elements like buttons and labels.

    auto layout tutorial

  • The Align menu is located to left of the Pin menu at the bottom right of the editor. This menu creates alignment constraints. You’d use this menu to vertically align a label relative to another view.

    auto layout tutorial

  • The Document Outline is located on the left side of the editor. This panel is used to view the hierarchy of a view’s layout. This is useful when trying to figure out the relationship of a view.

    auto layout tutorial

  • The Attributes Inspector is located in the middle utility pane on the right side of Xcode. This panel is used to customize a view’s attributes. You’d use this panel to change the background color of a view or the text of a button.

    auto layout tutorial

  • T-bars are Xcode’s way of visualizing constraints on a view. In the image below there are 3 T-bars that represent the button’s top, leading, and trailing constraints. These bars will also indicate if there are any warnings or errors by turning either yellow or red.

    auto layout tutorial

To start learning the ins and outs of Interface Builder, you’ll first create an empty iPhone application.

Trivia Tidbit: When iOS development first started, Interface Builder was actually a completely separate and stand alone application from Xcode!

Open up Xcode, and select Create a new Xcode project. Select the Single View App template and enter “Buttons” for the Product Name. Leave other options to their defaults.

Select Main.storyboard and make sure that Use Auto Layout, Use Trait Variations, and Use Safe Area Layout Guides are all enabled via the Storyboard’s File Inspector:

auto layout tutorial

New in iOS 11: Safe Area Layout Guides let you know the area of your content that is visible underneath anything that might be obscured by bars, such as navigation bars.

Alright it’s time to get your hands dirty! The best way to get comfortable with Auto Layout is to dive right in and create a user interface, so proceed to the next section and get ready to move fast and furious!

A little runtime excursion

Drag two buttons into the scene and give them a background color of yellow and green. Put the yellow button below the green button.

auto layout tutorial

Select the green button and use the Pin menu on the bottom right to make a constraint to its nearest bottom neighbor (40 points). Then select the lower button and do the same (8 points).

auto layout tutorial

Open the Align menu with the yellow button selected, and check Horizontally in Container. Now, select both buttons, and in the Align menu check Leading Edges and press Add Constraints.

auto layout tutorial

You might see a red circle with an arrow in it on the top left. Usually, this indicates an issue that you need to fix. Don’t worry about this for now, as you’ll fix it later. :]

Playing with this in Interface Builder is all well and good, but now you’ll see how this works at runtime. Add the following method to ViewController.swift:

@IBAction func buttonTapped(_ sender: UIButton) {
  if sender.title(for: .normal) == "X" {
    sender.setTitle("A very long title for this button", for: .normal)
  } else {
    sender.setTitle("X", for: .normal)
  }
}

This toggles between a long title and a short title for the button that triggered the event. Connect this action method to both of the buttons in Interface Builder. Ctrl-drag from each button to the view controller and select buttonTapped: in the popup.

Run the app and tap the buttons to see how it behaves. Perform the test in both portrait and landscape orientations.

auto layout tutorial

Regardless of which button has the long title and which has the short title, the layout always satisfies the constraints you have given it:

  • The lower button is always center-aligned in the window, horizontally.
  • The lower button always sits 8 points from the bottom of the window.
  • The top button is always 40 points above the lower button, and aligned with the lower button.

That is the entire specification for your user interface.

auto layout tutorial

For fun, remove the Leading Alignment constraint (select it in the
and press Delete on your keyboard), then select both buttons in Interface Builder and from the Align menu select the Trailing Edges option.

auto layout tutorial

Now run the app again and notice the differences.

auto layout tutorial

Repeat, but now choose Align\Horizontal Centers. That will always center the top button with respect to the bottom button. Run the app and see how the buttons act when you tap them.

auto layout tutorial

Auto Layout Issues: Sometimes Xcode cannot automatically resolve the layout you are trying to define. Most of the time Xcode hasn’t had a chance to re-calculate the view and you are stuck with an orange box indicating the best guess of where the layout should be. In this case clicking the Update Frames button should fix the problem.

auto layout tutorial

However sometimes the issue might be more tricky to resolve. Selecting the right-most Resolve Auto Layout Issues button presents a menu of options that can help by updating your existing constraints, add missing constraints, resetting your constraints to constraints suggested by Xcode, or clearing the constraints so that you can start over.

Intrinsic Content Size

The Pin menu has a Widths Equally option. If you set this constraint on two views, then Auto Layout will always make both views equally wide, based on which one is the largest.

Select both buttons and choose Pin\Equal Widths. This adds a new constraint to both buttons:

auto layout tutorial

Even though there are two T-bars, in the Document Outline this shows up as a single Equal Widths constraint:

auto layout tutorial

Run the app and tap the buttons. The buttons always have the same width, regardless of which one has the largest label.

auto layout tutorial

Of course, when both labels are very short, both buttons will shrink equally. After all, unless there is a constraint that prevents it, buttons will size themselves to fit their content exactly, no more, no less. What was that called again? Right, the intrinsic content size.

Before Auto Layout, you always had to tell buttons and other controls how big they should be, either by setting their frame or bounds properties or by resizing them in Interface Builder. But it turns out that most controls are perfectly capable of determining how much space they need, based on their content.

A label knows how wide and tall it is because it knows the length of the text that has been set on it, as well as the font size for that text. Likewise for a button, which might combine the text with a background image and some padding.

This is known as the intrinsic content size, and it is an important concept in Auto Layout. You have already seen it in action with the buttons. Auto Layout asks your controls how big they need to be and lays out the screen based on that information.

Usually you want to use the intrinsic content size, but there are some cases where you may not want to do that. You can prevent this by setting an explicit Width or Height constraint on a view.

Fixing the width

So what happens when one of the buttons has a fixed width constraint on it? Buttons calculate their own size, but you can override this by giving them a fixed width. Select the top button and choose Pin\Width from the menu. This adds a solid T-bar below the button:

auto layout tutorial

Because this sort of constraint only applies to the button itself, not to its superview, it is listed in the Document Outline below the button object. In this case, you have fixed the button to a width of 46 points:

auto layout tutorial

You cannot simply drag the button’s resize handles to make the button wider. If you do, you’ll end up with a whole bunch of orange boxes. If you make a change to the button’s frame, it’s up to you to make the constraints match. The alternative approach is to simply change the constraint.

Select the width = 46 constraint and go to the Attributes inspector. Change Constant to 80:

auto layout tutorial

Run the app and tap the buttons. What happens? The button text changes, but it gets truncated because there is not enough room:

auto layout tutorial

Because the top button has a fixed-width constraint and both buttons are required to be the same size, they will never shrink or grow.

Play around with this stuff for a bit to get the hang of pinning and aligning views. Get a feel for it, because not everything is immediately obvious. Just remember that there must always be enough constraints so that Auto Layout can determine the position and size for all views.

auto layout tutorial

Gallery example

You should now have an idea of what constraints are and how you can build up your layouts by forging relationships between the different views. In the following sections, you will see how to use Auto Layout and constraints to create layouts that meet real-world scenarios.

Pretend you want to make an app that has a gallery of your favorite programmers. It looks like this in portrait and landscape:

auto layout tutorial

The screen is divided into four equal quarters. Each quarter has an image view and a label. How would you approach this?

Download and open the starter project so you can get started. The sample project is a blank slate that includes the images you’ll need to display in the gallery. Open Main.storyboard.

Now select the iPhone SE size from the view as panel on the bottom left.

auto layout tutorial

From the Object Library, drag a plain View object onto the canvas. With the view selected, open the Size Inspector, and set the Width to 160, and Height to 284 points.

Next, in the Attributes inspector set the view’s background color to green:

auto layout tutorial

There are two main reasons why you would drop a plain UIView onto a storyboard: a) You’re going to use it as a container for other views, which helps with organizing the content of your scenes; or b) It’s a placeholder for a custom view or control, and you will also set its Class attribute to the name of your own UIView or UIControl subclass.

Select the green view and open the Pin menu. A popup appears which lets you add a variety of constraints, and you will also see a checkbox labeled Constrain to margins:

auto layout tutorial

Remember the blue guides that appear when you drag a view near to the edge of its superview? Instead of creating a constraint from the very edge of the view, you can create a constraint from the view’s margin. A view can define its own margin sizes which allows you to be more flexible about your layouts. For the purposes of this Auto Layout tutorial, you’ll stick to making constraints to the edges of the view. Go ahead and uncheck that box, and create 4 constraints to all four of the green view’s nearest neighbors in each direction by clicking the connector’s between the middle square and text field:

auto layout tutorial

This will create four new constraints between the green view and its superview, one for each side of the view. The actual spacing values may be different for you, depending on where you placed the view – you don’t have the change the values to match the ones above exactly. Click Add 4 Constraints to finish.

Your storyboard should now look something like this:

auto layout tutorial

Maybe you wondered why the constraint at the top of the view doesn’t go all the way up to the top of the screen:

auto layout tutorial

Instead it stops at the status bar. But since iOS 7 the status bar is always drawn on top of the view controller — it is no longer a separate bar — so what gives? When you created the constraint it didn’t actually attach to the top of the screen but the top of an invisible rectangle called the Safe Area.

The Safe Area is the part of the view controller that isn’t obscured by any bars like the status, navigation or tab bar. Because the navigation bar has a different height in landscape, the Safe Area resizes with the bar when the device is rotated. That makes it easy to place views relative to the navigation bar. The same goes for tab bars.

This view needs four constraints to keep it in place. Unlike a button or label, a plain UIView does not have an intrinsic content size. There must always be enough constraints to determine the position and size of each view, so this view also needs constraints to tell it what size it needs to be.

You may wonder, where are these size constraints? In this case, the size of the view is implied by the size of the superview. The constraints in this layout are a leading, trailing, top and bottom constraint, and these all have fixed lengths. You can see this in the Document Outline:

auto layout tutorial

The width of the green view is calculated by the formula “width of safe area minus (80 + 80)” and its height by the formula “height of safe area minus (13 + 163)”. The constraints are fixed, so the view has no choice but to resize. (Again, your values may be different depending on where you put the view.)

When you rotate the app, the dimensions of the superview change, so the view’s size changes with it. Build and run the app, and rotate the simulator to see how the view behaves.

auto layout tutorial

You may not always want your UIView to resize when the device rotates, so you can use constraints to give the view a fixed width and/or height. Do that now. Select the green view and click the Pin button; in the popup put checkmarks in front of Width and Height.

auto layout tutorial

Click Add 2 Constraints to finish. You have now added two new constraints to the view, a 160 point width constraint and a 372 point height constraint:

auto layout tutorial

Because width and height constraints apply to just this view, they are located in the Document Outline under the View. Usually, constraints express a relationship between two different views, but you can consider the width and height constraints to be a relationship between the green view and itself.

Run the app. Yup, looks good in portrait.

auto layout tutorial

Now flip over to landscape. Whoops! Not only does it not look like you wanted – the view has changed size again – but the Xcode debug pane has dumped a nasty error message that looks like this at the top:

Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this: (1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(...)
Will attempt to recover by breaking constraint....

Remember when I said that there must be enough constraints so that Auto Layout can calculate the positions and sizes of all the views? Well, this is an example where there are too many constraints. Whenever you get the error “Unable to simultaneously satisfy constraints”, it means that your constraints are conflicting somewhere.

Look at those constraints again:

auto layout tutorial

There are six constraints set on the green view, the four constraints you saw earlier (1-4) and the new width and height constraints that you have just set on it (5 and 6). So where is the conflict?

In portrait mode there shouldn’t be a problem because the math adds up. If you add the lengths of the leading and trailing constraints and the width constraints, then you should also end up at the width of the super view. Likewise for the vertical constraints.

But when you rotate the device to landscape, the math is off and Auto Layout doesn’t know what to do.

The conflict here is that either the width of the view is fixed and one of the margins must be flexible, or the margins are fixed and the width must be flexible. You can’t have both. So one of these constraints has to go. In the above example, you want the view to have the same width in both portrait and landscape, so the trailing Horizontal Space has got to go.

Remove the Safe Area.trailing = View.trailing at the right and the Safe Area.bottom = View.bottom at the bottom by selecting them in the Document Outline and hitting the delete key. The storyboard should look like this:

auto layout tutorial

Now the view has just the right number of constraints to determine its size and position — no more, no less. Run the app and verify that the error message is gone and that the view stays the same size after rotating.

auto layout tutorial

Painting the portraits

From the Object library, drag a Label onto the green view. Notice that now the guides appear within that green view, because it will be the superview for the label.

auto layout tutorial

Position the label against the bottom margin, spaced equally from the guides. Do this by dding a space constraint to anchor the label against the bottom, left and right of the green view, at 20 points distance. The quickest way is to use the Pin button and selecting the T-bar at the bottom, left and right, and entering 20 for the value:

auto layout tutorial

Now, with the Label selected, in the Attributes inspector, selected the Center Alignment button.

auto layout tutorial

It’s always important to have a right and left constraint on UILabels, since otherwise they would show outside their superview if the text is too long.

Notice that these new constraints are listed under the green view’s own Constraints section, not in the main view.

Drag a new Image View object onto the storyboard, and make the layout look like this:

auto layout tutorial

The image view’s top, left, and right edges are pinned to its superview, but its bottom is connected to the top of the label with a standard spacing of 8 points. If you’re unsure of how to do this, then follow these steps.

1. Drag the image view into the green view but don’t worry too much about its size or position:

auto layout tutorial

2. With the image view selected, press the Pin button and choose the following options:

auto layout tutorial

The top, left, and right T-bars are set to 20 points but the bottom one is set to 8 points.

The constraints you chose result in a different frame than the image view’s current position and size. Interface Builder will automatically adjust the frame as it adds the constraints and everything looks dandy:

auto layout tutorial

As mentioned above if you do end up with a misplaced frame, you can always use the Update Frames (1) or the Resolve Auto Layout Issues (2) button to fix it:

auto layout tutorial

Adding Images

Now you are ready to start adding the images and names to your view. You’ll use the bundled images included in the Assets.xcassets.

Open Main.storyboard. Select the Image View, and in the Attributes Inspector, set Ray as the Image. Also set the Content Mode to Aspect Fit. Then, set the Background to White Color

auto layout tutorial

Double click the Label, and set it’s title to “Ray”.

Next, select the green view’s height constraint, and in the Size Inspector, change its constant to 284. This will make sure it’s half as large as the screen.

Your layout should look like this:

auto layout tutorial

Notice that the constraints inside the green view turned red. This happened the moment you set the image on the image view. How come your layout is suddenly invalid? Fortunately you can take the guesswork out of it and let Xcode tell you exactly what’s wrong.

Click the small red arrow next to View Controller Scene in the Document Outline to view the issues:

auto layout tutorial

You have a Content Priority Ambiguity error. That’s quite the mouthful. This is what it means: If neither the image view nor the label has a fixed height, then Auto Layout doesn’t know by how much to scale each if the height of the green view should change.

Say at some point in your app the green view becomes 100 points taller. How should Auto Layout distribute these new 100 points among the label and the image view? Does the image view become 100 points taller while the label stays the same size? Or does the label become taller while the image view stays the same? Do they both get 50 points extra, or is it split 25/75, 40/60, or some other combination?

If you don’t solve this problem somehow then Auto Layout is going to have to guess and the results may be unpredictable.

The proper solution is to change the Content Hugging Priority of the label. You can imagine “hugging” here to mean “size to fit” – the bounds of the view will “hug” or be close to the intrinsic content size. A higher value here means the view will be less likely to grow and more likely to stay the same.

Go into the Size inspector for the label and set the Vertical Content Hugging Priority to 252. That makes it one higher than the priority of the image view. When the superview changes size, that means the image view will be the one to resize, and the label will stay pretty much the same size. Also change it’s Vertical Content Compression Resistance Priority to 751, which will make sure it doesn’t shrink.

auto layout tutorial

The T-bars should turn blue again and the Auto Layout warnings are gone.

Adding the other heads

Drag the green view into the main view’s top-left corner. Recall that the green view had Horizontal Space and Vertical Space constraints that determined its position in the parent view. It still has those and they cause the frame of the view to be misaligned.

To fix this, use the Resolve Auto Layout Issues button and choose Update Constraints Constants. This will update the constraints to match the frame.

auto layout tutorial

The leading constraint now has size 0 and is represented by a thick blue line at the left edge of the window. Even though the view sits completely in the corner, it still needs constraints to anchor it there:

Xcode was able to generate most of the constraints correctly however, you’ll need to modify the view’s top constraint. In the size inspector double click on the Align Top to Safe Area row in the Sibling & Ancestor Constraints section and set the Second Item to Superview:

Now you are going to start adding more views to show more people. Select the green view and tap ⌘D to duplicate it. Move the duplicate into the top-right corner:

Notice that the T-bars are red. When you made the duplicate, it lost its constraints for the X and Y position. To fix that, pin the view to the top and the right edges of the window (make sure to uncheck Constrain to margins).

auto layout tutorial

Duplicate two more times and put these copies in the bottom-left and bottom-right corners, respectively. The bottom-left view should have its leading and bottom pinned, while the bottom right should have its trailing and bottom edges pinned to the superview.

Time to make the screen more colorful. Select each of the green views and change their backgrounds like you did before, to a different color. Also change the labels’ titles and the images views’ images to represent the programmers. In the end, your screen should look something like this:

Those are some good-looking programmers! :]

Run the app on the iPhone SE simulator. It looks good in portrait, but not in landscape:

auto layout tutorial

It should be pretty obvious what went wrong: you’ve set a fixed width and height on the four brightly-colored container views, so they will always have those sizes, regardless of the size of their superview.

Select the fixed width and fixed height constraints from all four views and delete them (this is easiest in the Document Outline). If you run the app now, you’ll get something like this:

auto layout tutorial

Note: If you’re wondering why some of the views are larger than others, this is again related to the intrinsic content size. The size of the image determines how large the image view is; the size of the text determines how large the label is. Taken together with the constraints for the margins this determines the total size of each view.

To achieve your desired layout your going to set each view’s width and height to be 50% of the ViewController’s view. Start by Ctrl + dragging from the green Ray view to the white containing view:

While holding Shift click Equal Widths and Equal Heights then press Enter. This will allow you to select multiple items at once. Once those constraints are active Ray will fill the screen, which is not the intention. :]

In the size inspector click the edit button on the Equal Width to Superview row in the Sibling & Ancestor Constraints section:

auto layout tutorial

In the multiplier field enter 0.5 and press Enter. This will set the width of Ray’s view to be 50% of Ray’s container view. Repeat this for the Equal Height to Superview row and you should see that Ray is now the correct size:

Now repeat this for the remaining 3 views. Build and run. Everything looks great!

auto layout tutorial

Where To Go From Here?

You can find the final project for this Auto Layout tutorial here.

If you’ve made it this far through this Auto Layout tutorial, congratulations – you now know what Auto Layout is all about, and have experimented with the basics! But there’s a lot left to learn…

To continue, check out our Auto Layout tutorial video series.

If you have any questions or comments as you continue on you Auto Layout journey, join the forum discussion below!

The post Auto Layout Tutorial in iOS 11: Getting Started appeared first on Ray Wenderlich.

Android: An Introduction to Material Design with Kotlin

$
0
0

Update Note: This tutorial has been updated to Kotlin by Joe Howard. The original tutorial was written by Megha Bambra.

Material Design with Kotlin!

Material Design with Kotlin!

Google’s material design brings with it exciting ways to delight your users with a visually appealing Android app. But wait—what is material design?

Google has described it as an interface that incorporates “tactile surfaces, bold graphic design and fluid motion to create beautiful, intuitive experiences.” Material design is the “user experience philosophy” for Android apps!

In this tutorial, you’ll integrate material design into an app called Travel Wishlist. Along the way, you’ll learn how to:

  • Implement the material theme;
  • Build dynamic views using widgets like RecyclerView and CardView;
  • Use the Palette API to generate color schemes that you can use for text or background views;
  • Create delightful interactions using Android animation APIs.

This tutorial assumes you have a basic familiarity with Android programming including Kotlin, XML, Android Studio and Gradle. If you’re completely new to Android, you might want to go through our Beginning Android Development series and Kotlin Introduction first.

To follow along with this tutorial, you’ll need to use Android Studio 3.0 Beta 2 or later and Kotlin 1.1.4-2 or later.

Let’s get started!

Getting Started

Download the starter project, then fire up Android Studio.

To import the starter project, first select Open an existing Android Studio project from the Welcome to Android Studio window:

Then select the downloaded project and click Open:

Travel Wishlist will be a very simple app. Users will see a grid of pictures from locations around the world, and be able to tap each picture to add notes about what to do and see.

Build and run the starter project, and you should see a screen with the most basic of interfaces:

Right now, the world is empty! You’re going to add material components to this existing project, including dynamic views, color schemes and animations that will truly complement the beauty of the photos in your dataset.

Open build.gradle for the app module and add RecyclerView, CardView, Palette, and Picasso to your dependencies:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.0.1'
    implementation 'com.android.support:recyclerview-v7:26.0.1'
    implementation 'com.android.support:cardview-v7:26.0.1'
    implementation 'com.android.support:palette-v7:26.0.1'
    implementation 'com.squareup.picasso:picasso:2.5.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.0'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.0'
}

Here you’re simply declaring the dependencies that you’ll use throughout the rest of the tutorial. The first few of the added dependencies are Google-provided APIs, but the final one, Picasso, is a fantastic image downloading and caching library provided by the good folks at Square.

With the dependencies declared, it’s time to begin incorporating Material Design into your app!

Setting Up the Theme

Before doing anything else, you will update the theme. Open style.xml under the res/values directory. By default, the theme selected is Theme.AppCompat.Light.DarkActionBar. Add the following items inside the theme tag:

<item name="android:navigationBarColor">@color/primary_dark</item>
<item name="android:displayOptions">disableHome</item>

Android automatically applies colorPrimary to the action bar, colorPrimaryDark to status bar and colorAccent to UI widgets like text fields and checkboxes.

In the code above, you also alter the color of the navigation bar. For android:displayOptions, you pass disableHome to accommodate the screen layouts in this sample app.

Build and run, and you’ll see the new color scheme in the app.

It’s a subtle change, but like every trip on your travel wishlist, upgrading this design begins with a single step!

Using RecyclerView and CardView

To give your users a window into all the cool places they might go, you need a view. You can use RecyclerView as a replacement for ListView, but it’s much more versatile than that. Google describes RecyclerView as “a flexible view for providing a limited window into a large data set.” In this section, you’re going to demonstrate this by switching the view from a list to a custom grid that uses the same data source which supplies the the users locations.

Implementing a Recycler View in XML

First, open activity_main.xml and add the following inside the LinearLayout tag:

<android.support.v7.widget.RecyclerView
  android:id="@+id/list"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/light_gray"/>

Here you’re adding a RecyclerView to the activity’s layout, and specifying it should match the entire size of the parent view.

Initializing a Recycler View and Applying a Layout Manager

Before adding Kotlin code, configure Android Studio so that it automatically inserts import statements to save you having to add each one manually.

Go to Preferences\Editor\General\Auto Import and select the Add unambiguous imports on the fly checkbox. In MainActivity.kt, add the following to the top of the class:

lateinit private var staggeredLayoutManager: StaggeredGridLayoutManager

Here you’re simply declaring a property to hold a reference to the LayoutManager.

Next, add the following to the bottom of onCreate():

staggeredLayoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL)
list.layoutManager = staggeredLayoutManager

In the code above, you set the layout manager of the RecyclerView to a StaggeredGridLayoutManager, which you’ll use to create two types of vertically staggered grids. Here you start with the first type, passing 1 for the span count and StaggeredGridLayoutManager.VERTICAL for the orientation. A span count of 1 makes this a list rather than a grid, as you’ll soon see. Later, you’ll add a compact grid formation with two columns.

Note that you’re using Kotlin Android Extensions to find list, so there is no need for a call to findViewById(). Make sure that the following line is present in your import statements, as it should be automatically added when you type in list:

import kotlinx.android.synthetic.main.activity_main.*

Creating Rows and Cells Using a Card View

CardView provides a consistent backdrop for your views, including rounded corners and a drop shadow. You’re going to implement it for the row/cell layout of your RecyclerView. By default, CardView extends FrameLayout and therefore includes the ability to host other child views.

From the res\layout directory, create a new Layout resource file and call it row_places.xml. Press OK to create it.

To create your desired cell layout, replace all the contents of this file with the code below:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:card_view="http://schemas.android.com/apk/res-auto"
  android:id="@+id/placeCard"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_margin="8dp"
  card_view:cardCornerRadius="@dimen/card_corner_radius"
  card_view:cardElevation="@dimen/card_elevation">

  <ImageView
    android:id="@+id/placeImage"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:scaleType="centerCrop" />

  <!-- Used for the ripple effect on touch -->
  <LinearLayout
    android:id="@+id/placeHolder"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:selectableItemBackground"
    android:orientation="horizontal" />

  <LinearLayout
    android:id="@+id/placeNameHolder"
    android:layout_width="match_parent"
    android:layout_height="45dp"
    android:layout_gravity="bottom"
    android:orientation="horizontal">

    <TextView
      android:id="@+id/placeName"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="center_vertical"
      android:gravity="start"
      android:paddingStart="10dp"
      android:paddingEnd="10dp"
      android:textAppearance="?android:attr/textAppearanceLarge"
      android:textColor="@android:color/white" />

  </LinearLayout>

</android.support.v7.widget.CardView>

By adding xmlns:card_view="http://schemas.android.com/apk/res-auto", you can define attributes like card_view:cardCornerRadius and card_view:cardElevation that are responsible for giving Material Design enabled Android apps their signature card-like look.

Notice that for mainHolder, you’ve added ?android:selectableItemBackground as the background. This enables the ripple effect animation when the user touches a cell, as seen in many Android apps now. You’ll get to see it in action soon.

Implementing an Adapter for a Recycler View

You’re going to use an adapter for the RecyclerView to bind data to the view. In the main/java folder, right-click on the package com.raywenderlich.android.travelwishlist package and select New\Kotline File/Class. Call the class TravelListAdapter.

Add the following code to the class, taking care to preserve the package statement at the top of the file:

// 1
class TravelListAdapter(private var context: Context) : RecyclerView.Adapter<TravelListAdapter.ViewHolder>() {

  override fun getItemCount(): Int {
  }

  override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
  }

  override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
  }

  // 2
  inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
  }
}

A couple of things are happening above:

  1. You make TravelListAdapter extend Recycler.Adapter so that you can implement logic for the override methods you’ll add soon. You also setup the constructor with a Context that will be passed in when you create an instance of TravelListAdapter in MainActivity, which you’ll do a bit later in the tutorial.
  2. You create the ViewHolder class. Whereas the use of the ViewHolder pattern is optional in ListView, RecyclerView enforces it. This improves scrolling and performance by avoiding findViewById() for each cell.

Update the RecyclerView.Adapter methods in TravelListAdapter to the following:

// 1
override fun getItemCount() = PlaceData.placeList().size

// 2
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
  val itemView = LayoutInflater.from(parent.context).inflate(R.layout.row_places, parent, false)
  return ViewHolder(itemView)
}

// 3
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
  val place = PlaceData.placeList()[position]
  holder.itemView.placeName.text = place.name
  Picasso.with(context).load(place.getImageResourceId(context)).into(holder.itemView.placeImage)
}

Here’s what’s happening:

  1. getItemCount() returns the number of items from your data array. In this case, you’re using the size of the PlaceData.placeList().
  2. onCreateViewHolder(...) returns a new instance of your ViewHolder by passing an inflated view of row_places.
  3. onBindViewHolder(...) binds the Place object to the UI elements in ViewHolder. You’ll use Picasso to cache the images for the list.

Add a field in MainActivity that will hold a reference to your adapter:

lateinit private var adapter: TravelListAdapter

And then create an instance of your adapter and pass it to the RecyclerView at the bottom of onCreate(), just after you configure the layout manager:

adapter = TravelListAdapter(this)
list.adapter = adapter

Now build and run the app, and you’ll see a populated list of places.

Which place is calling your name? I like the look of that turquoise water. But wherever you want to go, you’ll want to cultivate your dream by taking notes about what to do there. First, you need to make the cells respond to a user’s touch.

alltheplaces

Implementing a Click Interface for Each Cell

Unlike ListView, RecyclerView doesn’t come with an onItemClick interface, so you have to implement one in the adapter. In TravelListAdapter, create a property to hold an instance of OnItemClickListener. Add the following to the top of TravelListAdapter:

lateinit var itemClickListener: OnItemClickListener

Now implement View.OnClickListener by adding the interface to the ViewHolder inner class definition like this:

inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {

Then add the following method stub to the inner ViewHolder class:

override fun onClick(view: View) {

}

Hook the two up by adding the following init block to the top of ViewHolder:

init {
  itemView.placeHolder.setOnClickListener(this)
}

Above, you initiate setOnClickListener for placeHolder and implement the onClick override method.

You need to do a few more things to implement the onClick interface for the RecyclerView. First, after the inner ViewHolder class definition add the following:

interface OnItemClickListener {
  fun onItemClick(view: View, position: Int)
}

Next, add the setter method of the onClickListener to TravelListAdapter:

fun setOnItemClickListener(itemClickListener: OnItemClickListener) {
  this.itemClickListener = itemClickListener
}

Now implement the logic in the empty onClick() stub within the inner ViewHolder class:

override fun onClick(view: View) = itemClickListener.onItemClick(itemView, adapterPosition)

In MainActivity, create an instance of OnItemClickListener above onCreate():

private val onItemClickListener = object : TravelListAdapter.OnItemClickListener {
  override fun onItemClick(view: View, position: Int) {
    Toast.makeText(this@MainActivity, "Clicked " + position, Toast.LENGTH_SHORT).show()
  }
}

Finally, set the listener to the adapter by adding the following code to the bottom of onCreate(), just after where you set the adapter:

adapter.setOnItemClickListener(onItemClickListener)

Build and run. Now when you tap a cell you’ll see ripple effect every time you touch a row, and a Toast notification displaying the position of the cell in the list.

From List to Grid and Back

StaggeredLayoutManager lets you add versatility to your layouts. To change your existing list to a more compact two-column grid, you simply have to change the spanCount of the StaggeredLayoutManager in MainActivity.

In toggle(), add the following to the top of the showGridView():

staggeredLayoutManager.spanCount = 2

And now add the following to the top of showListView():

staggeredLayoutManager.spanCount = 1

Here you’re simply switching between single and double span counts, which displays single and double columns respectively.

Build and run and use the action bar button to toggle between list and grid views.

Using the Palette API in the List

Now you can add some interesting Material Design features into the mix, starting with the Palette API. Head back to TravelListAdapter, where you’ll define a background color for placeNameHolder that will be determined dynamically using the colors in the image.

Add the following to the bottom of onBindViewHolder(...):

val photo = BitmapFactory.decodeResource(context.resources, place.getImageResourceId(context))
Palette.from(photo).generate { palette ->
  val bgColor = palette.getMutedColor(ContextCompat.getColor(context, android.R.color.black))
  holder.itemView.placeNameHolder.setBackgroundColor(bgColor)
}

The generate(...) method creates a color palette in the background, and is passed a lambda that is called when the palette has been generated. Here you can access the generated color palette and set the background color of holder.itemView.placeNameHolder. If the color doesn’t exist, the method will apply a fallback color — in this case, android.R.color.black.

Build and run to see the Palette API in action!

Note: The Palette API can extract the following color profiles from an image:
  • Vibrant
  • Dark Vibrant
  • Light Vibrant
  • Muted
  • Dark Muted
  • Light Muted

I encourage you to experiment with these. Instead of palette.getMutedColor(...), try palette.getVibrantColor(...), palette.getDarkVibrantColor(...) and so on.

Using the New Material APIs

In this section, you’ll use DetailActivity and its corresponding activity_detail layout, and make them cooler by infusing some of the new Material Design APIs.

First, you’ll want to see how the detail view currently looks in the starter project. To see this, first add the following to the companion object of DetailActivity:

fun newIntent(context: Context, position: Int): Intent {
  val intent = Intent(context, DetailActivity::class.java)
  intent.putExtra(EXTRA_PARAM_ID, position)
  return intent
}

Then, go to MainActivity and replace the Toast in onItemClick(...) of onItemClickListener with the following:

startActivity(DetailActivity.newIntent(this@MainActivity, position))

You can pass the position of the place object via the intent so that DetailActivity can retrieve the information and use it to layout the interface. That’s what you’re doing here.

Build and run.

There isn’t anything crazy going on here (yet!), but you’ve got a nice foundation on which to start adding those highly anticipated Material Design APIs. You also see a cool FloatingActionButton, one of the widgets introduced by Material Design.

Adding a Reveal Animation

Now you want to give your users the ability to add notes about what they’d like to do in each of these stunning places. For this, activity_detail.xml already has an edittext that is hidden by default. When a user taps the FAB, it reveals itself with a cool animation like below:

Open DetailActivity. There are two methods you have yet to implement:

  • revealEditText()
  • hideEditText()

First, add the following lines inside revealEditText():

val cx = view.right - 30
val cy = view.bottom - 60
val finalRadius = Math.max(view.width, view.height)
val anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, 0f, finalRadius.toFloat())
view.visibility = View.VISIBLE
isEditTextVisible = true
anim.start()

The two int values are getting the x and y positions of the view with a slight offset. This offset gives the illusion that the reveal is happening from the direction of your FAB.

Next, the radius gives the reveal the circular outline that you can see in the GIF above. All of these values — the x-position, y-position and the radius — you pass into the animation instance. This animation is using ViewAnimationUtils, which gives you the ability to create this circular reveal.

Since the EditText view is initially hidden, you set the view’s visibility to VISIBLE and set your boolean check isEditTextVisible to true. Finally, you can call start() on the animation.

To dismiss the view, add the following to hideEditText():

val cx = view.right - 30
val cy = view.bottom - 60
val initialRadius = view.width
val anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius.toFloat(), 0f)
anim.addListener(object : AnimatorListenerAdapter() {
  override fun onAnimationEnd(animation: Animator) {
    super.onAnimationEnd(animation)
    view.visibility = View.INVISIBLE
  }
})
isEditTextVisible = false
anim.start()

Here your goal is to hide the view and show the circular animation in the opposite direction. Therefore, you make the initial radius the width of the view and the ending radius 0, which shrinks the circle.

You want to show the animation first and then hide the view. To do this, you implement an animation listener and hide the view when the animation ends.

Now build and run and see this animation in action!

Note: If the keyboard presents itself, you’ll need to dismiss it explicitly to see the effect without obstruction. Comment out the call to inputManager.showSoftInput(...) in DetailActivity, but don’t forget to uncomment it. Oh, and don’t worry that your button doesn’t show the plus icon yet, you’ll fix that soon.

Morphing a Bezier Path for a Floating Action Button

Now that you have your reveal animation hiding and showing the edit text field, you can coordinate the icon on your FAB to look and respond just like the one shown below:

The starter project includes the vector paths for the plus and checkmark icons. You’ll learn how to animate – or morph – the paths from the plus to the checkmark, and vice versa.

Under the res/drawables directory, create a new resource file by going to New\Drawable resource file. Call it icn_morph and define animated-vector as the root element:

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:drawable="@drawable/icn_add">
</animated-vector>

animated-vector requires an existing android:drawable. In this case, the animated vector will start with a plus sign and morph into a checkmark, so you’ll set the drawable to icn_add.

Now for the actual morphing, add the following inside the animated-vector tag:

<target
  android:animation="@anim/path_morph"
  android:name="sm_vertical_line" />
<target
  android:animation="@anim/path_morph_lg"
  android:name="lg_vertical_line" />
<target
  android:animation="@anim/fade_out"
  android:name="horizontal_line" />

With the code above, you are essentially transforming the vertical line of the plus icon into a checkmark while fading out the horizontal line, as the diagram below illustrates:

Screen Shot 2015-04-22 at 12.59.57 AM

Furthermore, the vertical line is comprised of two paths, a smaller vertical line and a larger one:

Screen Shot 2015-04-22 at 1.11.49 AM

You can see from the diagram above that you can transform the first two targets, sm_vertical_line and lg_vertical_line, into a checkmark by drawing their paths at different angles, which is exactly what you do in the previous code block, along with fading out horizontal_line.

Next, you need to reverse this animation to transform the checkmark back into a plus sign. Create another drawable resource file, this time calling it icn_morph_reverse, and replace it’s contents with the following:

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:drawable="@drawable/icn_add">
  <target
    android:animation="@anim/path_morph_reverse"
    android:name="sm_vertical_line"/>
  <target
    android:animation="@anim/path_morph_lg_reverse"
    android:name="lg_vertical_line" />
  <target
    android:animation="@anim/fade_in"
    android:name="horizontal_line" />
</animated-vector>

The two lines that make up the final vertical line in the plus icon will now morph back into their original states and the horizontal line will fade into view, creating a smooth effect.

Now, to complete the animation. Open DetailActivity.kt and add the following to onClick(), at the end of the if branch before the else:

addButton.setImageResource(R.drawable.icn_morph)
val animatable = addButton.drawable as Animatable
animatable.start()

Here you set the image resource of the button to the icn_morph drawable you created earlier, extract the animatable from it, and then kick-off the animation.

Finally, add the following to the very bottom of the else branch:

addButton.setImageResource(R.drawable.icn_morph_reverse)
val animatable = addButton.drawable as Animatable
animatable.start()

Here you’re doing almost exactly the same as the previous step, except you assign icn_morph_reverse as the image resource so the animation plays out in the opposite direction.

Along with morphing the icon, the user’s click also adds the text from todoText to the toDoAdapter and refreshes the place activity list. This is not yet visible because of the white text, but in the next section, you’ll add vibrant color to your views so that the text stands out.

Build and run, and watch the magic unfold before your eyes! The FAB icon now morphs between a checkmark and a plus sign when it’s tapped.

Adding Dynamic Colors to Views Using Palette API

It’s time to add colors to this view using the Palette API. And not just any colors—as before, they will be dynamic colors!

In DetailActivity, flesh out colorize() by adding the following:

val palette = Palette.from(photo).generate()
applyPalette(palette)

Just like you did previously, you generate a palette from a photo – although this time you do it synchronously – and then pass that palette onto applyPalette(). Replace the existing method stub for applyPalette() with this code:

  private fun applyPalette(palette: Palette) {
    window.setBackgroundDrawable(ColorDrawable(palette.getDarkMutedColor(defaultColor)))
    placeNameHolder.setBackgroundColor(palette.getMutedColor(defaultColor))
    revealView.setBackgroundColor(palette.getLightVibrantColor(defaultColor))
  }

Here you’re you’re using the dark muted color, the muted color, and the light vibrant color as the background colors of the window, title holder, and reveal view respectively.

Finally, to kick-off this chain of events add the following line to the bottom of getPhoto():

colorize(photo)

It’s that time again… build and run your app! You can see the detail activity is now using a color scheme derived from the palette of the associated image.

Activity Transitions With Shared Elements

We’ve all seen and wondered about those nice image and text transitions in Google’s app which have been updated to use Material Design. Well, wait no more—you’re about to learn the intricacies of achieving smooth animation.

Note: Activity transitions, together with shared elements, allow your app to transition between two activities that share common views. For example, you can transition a thumbnail on a list into a larger image on a detail activity, providing continuity of the content.

Between the places list view, MainActivity, and the places detail view, DetailActivity, you’re going to transition the following elements:

  • The image of the place;
  • The title of the place;
  • The background area of the title.

Open row_places.xml and add the following to the declaration of the ImageView tag with an id of placeImage:

android:transitionName="tImage"

And then add this to the LinearLayout tag with an id of placeNameHolder:

android:transitionName="tNameHolder"

Notice that placeName doesn’t have a transition name. This is because it is the child of placeNameHolder, which will transition all of its child views.

In activity_detail.xml, add a transitionName to the ImageView tag with the id placeImage:

android:transitionName="tImage"

And, in a similar fashion, add a transitionName to the LinearLayout tag that has an id of placeNameHolder:

android:transitionName="tNameHolder"

Shared elements between activities that you want to transition should have the same android:transitionName, which is what you’re setting up here. Also, notice that the size of the image, as well as the height of the placeNameHolder are much larger in this activity. You’re going to animate all of these layout changes during the activity transition to provide some nice visual continuity.

In onItemClickListener() found in MainActivity, update the method to the following:

override fun onItemClick(view: View, position: Int) {
  val intent = DetailActivity.newIntent(this@MainActivity, position)

  // 1
  val placeImage = view.findViewById<ImageView>(R.id.placeImage)
  val placeNameHolder = view.findViewById<LinearLayout>(R.id.placeNameHolder)

  // 2
  val imagePair = Pair.create(placeImage as View, "tImage")
  val holderPair = Pair.create(placeNameHolder as View, "tNameHolder")

  // 3
  val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this@MainActivity,
    imagePair, holderPair)
  ActivityCompat.startActivity(this@MainActivity, intent, options.toBundle())
}

After adding this code, you will need to manually add the following import statement to the top of the file as Android Studio cannot automatically determine that this is the intended package.

import android.support.v4.util.Pair

There are a couple of things to highlight here:

  1. You get an instance of both placeImage and placeNameHolder for the given position of the RecyclerView. You’re not relying on Kotlin Android Extensions here since you need the placeImage and placeNameHolder from the specific view.
  2. You create a Pair containing the view and the transitionName for both the image and the text holder view. Note that you will once again have to manually add the import statement to the top of the file: android.support.v4.util.Pair.
  3. To make the activity scene transition with shared views, you pass in your Pair instances and start the activity with your options bundle.

Build and run to see the image transition from the main activity to the detail activity:

However, the animation is a bit jumpy in two areas:

  • The FAB button suddenly appears in DetailActivity.
  • If you tap on a row under the action or navigation bar, that row appears to jump a bit before it transitions.

You’ll solve the FAB button issue first. Open DetailActivity.kt and add the following to windowTransition():

window.enterTransition.addListener(object : Transition.TransitionListener {
  override fun onTransitionEnd(transition: Transition) {
    addButton.animate().alpha(1.0f)
    window.enterTransition.removeListener(this)
  }

  override fun onTransitionResume(transition: Transition) { }
  override fun onTransitionPause(transition: Transition) { }
  override fun onTransitionCancel(transition: Transition) { }
  override fun onTransitionStart(transition: Transition) { }
})

The listener you add to the enter transition is triggered when the window transition ends, which you use to fade in the FAB button. For this to be effective, set the alpha to 0 for the FAB in activity_detail.xml:

android:alpha="0.0"

Build and run! You’ll notice the FAB transition is much smoother!:

As for the action bar and navigation bar issues, begin by updating styles.xml, to set the parent theme to Theme.AppCompat.Light.NoActionBar:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

Since there is no action bar defined in styles.xml, you’ll have to add it using individual XML views.

Open activity_main.xml and add the following inside LinearLayout, just above the RecyclerView tag:

<include layout="@layout/toolbar" />

This simply includes a toolbar layout that's provided as part of the starter project into the current layout. Now you need to make a similar change to the detail activity's layout.

Open activity_detail.xml and add the following at the very bottom of the first FrameLayout, just below the closing tag of the inner LinearLayout:

<include layout="@layout/toolbar_detail"/>

Next in MainActivity, you need to initialize the toolbar. Add the following to the bottom of the onCreate() method:

setUpActionBar()

Here you assign the result of the findViewById call to the new field, and then call setUpActionBar(). At the moment it's just an empty method stub. Fix that now by adding the following to setUpActionBar():

    setSupportActionBar(toolbar)
    supportActionBar?.setDisplayHomeAsUpEnabled(false)
    supportActionBar?.setDisplayShowTitleEnabled(true)
    supportActionBar?.elevation = 7.0f

Here you set the action bar to be an instance of your custom toolbar, set the visibility of the title, disable the home button, and add a subtle drop shadow by setting the elevation.

Build and run. You'll notice that nothing much has changed, but these changes have laid the foundations of properly being able to transition the toolbar.

Open MainActivity and replace the existing onItemClickListener with this one:

private val onItemClickListener = object : TravelListAdapter.OnItemClickListener {
  override fun onItemClick(view: View, position: Int) {
    // 1
    val transitionIntent = DetailActivity.newIntent(this@MainActivity, position)
    val placeImage = view.findViewById<ImageView>(R.id.placeImage)
    val placeNameHolder = view.findViewById<LinearLayout>(R.id.placeNameHolder)

    // 2
    val navigationBar = findViewById<View>(android.R.id.navigationBarBackground)
    val statusBar = findViewById<View>(android.R.id.statusBarBackground)

    val imagePair = Pair.create(placeImage as View, "tImage")
    val holderPair = Pair.create(placeNameHolder as View, "tNameHolder")

    // 3
    val navPair = Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME)
    val statusPair = Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME)
    val toolbarPair = Pair.create(toolbar as View, "tActionBar")

    // 4
    val pairs = mutableListOf(imagePair, holderPair, statusPair, toolbarPair)
    if (navigationBar != null && navPair != null) {
      pairs += navPair
    }

    // 5
    val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this@MainActivity,
      *pairs.toTypedArray())
    ActivityCompat.startActivity(this@MainActivity, transitionIntent, options.toBundle())
  }
}

The differences between the original implementation and this one are thus:

  1. You've renamed the intent to provide more context;
  2. You get references to both the navigation bar and status bar;
  3. You've created three new instances of Pair - one for the navigation bar, one for the status bar, and one for the toolbar;
  4. You've protected against an IllegalArgumentException that occurs on certain devices, such as the Galaxy Tab S2, on which navPair is null.
  5. And finally you've updated the options that are passed to the new activity to include the references to the new views. You've used the Kotlin spread operator * on pairs, after changing it to a typed array.

Great! Build and run, and you’ll see a much smoother animation:

Now if you tap on a row under the action/toolbar or navigation bar, it doesn't jump before the transition; it transitions with the rest of the shared elements, which is much more pleasing to the eye. Switch to the grid view and you'll notice that the transitions work very nicely with that layout as well.

Ta-da! Here is a video of the final app in action:

Where to Go From Here?

Be proud of yourself: You’ve created a full-blown Android material app! To challenge yourself, try the following:

  • Use StaggeredLayoutManager to make a grid with three columns instead of two.
  • Experiment with the Palette API in both MainActivity and DetailActivity using different palette options.
  • Add a button on the place list and transition it to the detail view as a shared element—perhaps a favorite button.
  • Make those transitioning animations even cooler—check out Android's Newsstand app and see how it transitions from a grid to a detail view with the reveal animation. You have all the code here to replicate that.
  • Try to create all the morphing animations you did here, but using animated-vectors.

And of course, apply this knowledge to your apps so that they can be as cool as this one. :]

To find out more about Material Design then be sure to check out Google's recently redesigned Google Design website.

You can get the completed project here.

Feel free to share your feedback or ask any questions in the comments below or in the forums.

The post Android: An Introduction to Material Design with Kotlin appeared first on Ray Wenderlich.


Video Tutorial: Beginning Firebase Part 4: JSON Challenge

Video Tutorial: Beginning Firebase Part 5: References

Video Tutorial: Beginning Firebase Part 6: Saving Data

Video Tutorial: Beginning Firebase Part 7: Reading Data

Video Tutorial: Beginning Firebase Part 8: Reading Data Challenge

Viewing all 4374 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>