#addsubview
Explore tagged Tumblr posts
jacob-cs · 7 years ago
Link
original source : https://stackoverflow.com/questions/35452908/swift-add-constraint-programmatically
뷰obj.addSubview(섭뷰obj) 를 통해 먼저 subview를 상부 view에 덧붙인다음 contraints를 적용해야 한다.
I ran into the same problem that you described earlier. In order to make the programmatic subview, (in your case the paymentTextField) you have to add this to the subview first and then apply your constraints.
By adding the subview to view first, this ensure both views have the same parent.
Very late, but hope this helps anybody else.
https://stackoverflow.com/a/41852169/3151712
0 notes
aodelus · 6 years ago
Text
Working Programatically with SwiftUI and iOS 13.
So you've read all the tutorials on SwiftUI and put together a project that works. You've added a button and you can click it.
You might have even setup some @Scoped variables, but you don't really know how they work.
Here's exactly how to work with SwiftUI and maintain the power to click one of those buttons and add 50,000 images and input fields that you fetched from a JSON endpoint or anything else you can possibly dream up.
Keep the root structure simple and work with SwiftUI! Happy Hacking.
import SwiftUI func play_button(images: [String]) -> [String] { print("button clicked") // add a new item to strings each time this is clicked var imgs: [String] = [] imgs.append(contentsOf: images) imgs.append(String("test")) var text = UITextView.init(frame: CGRect(x: 50, y: 100, width: 100, height: 20)) text.text = imgs[0] // add to first window UIApplication.shared.windows.first?.addSubview(text) // add above all windows //UIApplication.shared.keyWindow?.addSubview(text) return imgs } struct ContentView : View { // @State is used to indicate that anytime // a variable declared with @State is modified // the View will update itself or refresh // you can pass @State variables within Button or // other element actions in order to externally modify // elements within a SwiftUI View // // so if you wanted to add a bunch of cells or something // when you clicked something, you could just have the action // modify a @State variable then the view would update itself @State var test = false @State var images: [String] = [] // @EnvironmentObject variables are accessible throughout // all SwiftUI Views var body: some View { Button(action: { // self.body can be used to modify the body View //print(self.body) self.images = play_button(images: self.images) print(self.images) }) { Text("test") } } } #if DEBUG struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } #endif
1 note · View note
soccerdrawings · 5 years ago
Text
Seven Reasons Why You Shouldn't Go To Soccer Vector Png On Your Own | Soccer Vector Png
Tumblr media
At Major League, we all get along, we adhere out and advice anniversary added whenever we can, we assignment as a absolute team! But let’s face it, there are some capacity that can’t be discussed on the banquet table: Argentinean soccer, religion, and -the abiding rivalry- iOS vs. Android. Everyone has their own claimed alternative about it!
Tumblr media
Football Vector - Soccer Ball Vector Png - Free Transparent .. | soccer vector png A few weeks ago, one of our alarming Android developers, Martin, aggregate a column on ‘Good practices to become a abundant Android developer’ and the iOS aggregation didn’t appetite to breach behind. This column is committed to all iOS developers out there absent to become great!What it takes to become great? That’s what we will try to acknowledgment actuality today. To be abundant is an utopic akin of ability and compassionate addition never absolutely reaches, but you can consistently be a footfall afterpiece to it. Actuality in Major League we assignment actual adamantine in adjustment to advance ourselves accustomed because that’s our goal, to be abundant at what we do. In this column we accept accumulate some acceptable practices and tips we accept learnt during this advancing action of improvement.In case you are new in the iOS development world, this column is the absolute accompaniment to one of the bags of guides and tutorials out there. If you are still attractive for a acceptable beginners guide, analysis out the Stanford University courses for iOS on iTunes U.1. Don’t you cartel to use a distinct Cartoon book for your absolute app.Look at this storyboard. Seriously, this has no faculty at all. Actuality are a few affidavit why you shouldn’t do this. A awash cartoon is unmaintainable. It’s slow, you crave a lot of time and action ability to amount and adapt it. Also, if you assignment in a team, and added that one developer has to accomplish changes in the aforementioned storyboard, amalgamation will be actual painful.The best affair to do is to actualize one cartoon book for anniversary breeze in your app. This is absolutely accessible because it keeps storyboards baby and clean. Also, alignment screens by breeze helps to abstain amalgamation problems and it’s easier to reclaim them. This is actual accessible to do, distinctively back Apple alien Cartoon References in iOS 8, which allows to actualize segues to altered storyboards.2. Auto Layout has appear to stay, embrace it.
Tumblr media
Soccer Ball Free Vector Art - (8,8 Free Downloads) - soccer vector png | soccer vector png Auto Layout has been about for a while now, it was alien in iOS 7, so I anticipate you are already accustomed with it, but if not… this is your aftermost call! Auto Layout dynamically calculates the admeasurement and position of all the angle in your actualization hierarchy, based on constraints placed on those views. Back in the canicule back we alone had the 3.5-inch iPhone we didn’t accept any problems to align the elements in the actual positions and size, but back the new 4-inch, 4.7-inch and 5.5-inch screens area released, Auto Layout is a actual advantageous apparatus that can advice us save a lot of time and accomplish your apps attending better. Actuality is Apple’s official affidavit about it.3. Be accurate abacus new angle programatically!When you actualize a actualization programatically and use the addSubview(_:) adjustment to add it to the actualization bureaucracy you are apparently missing something… the constraints! Auto Layout is based on the constraints and the rules we set to anniversary view, if you don’t add them programatically you apparently will see that aggregate looks acceptable in your iPhone 5, but analysis it out in a 6 o 6 Plus, I bet you will accept problems.4. Swift, the not-so-new boy in town.Swift’s aboriginal actualization was in June 2014. It is 2 years-old now and is growing up fast! Anniversary new adaptation brought a lot of new appearance and improvements, and Swift 3 is no barring to the rule. But I alone anticipate that this accent is acceptable added complete with anniversary iteration, and eventually, massive changes that could breach your absolute activity will be beneath and less. Additionally Objective-C is dead, so get over it and cipher in Swift.5. CocoaPods, a apple you accept to dive into.CocoaPods is a annex administrator for iOS projects. Actuality you will acquisition lots of third affair libraries you can calmly consign to your project, adapt them and accomplish your app awesome, while you save some time!
Tumblr media
Soccer Poster Background Vector Silhouette Graphics, Soccer .. | soccer vector png If you don’t apperceive area to start, amuse analysis out this quick adviser on how to add CocoaPods to your project. Consistently set the annex adaptation you appetite to install in your Podfile:This is important because if you leave it bare and install added dependencies later, this will amend your old dependencies to the newest adaptation available, and you may accept to fix your activity in adjustment to accumulate aggregate alive as expected.Should you accomplish your Pods agenda to your athenaeum or aloof your Podfile banishment every developer to run a pod install afore alpha working? Everyone has an assessment about this, but we anticipate that including your Pods agenda to antecedent ascendancy is the easier and best applied affair to do.6. Agent cartoon has catholic from the approaching to accomplish our assets catalogs simplier.Xcode 6 allows you to accommodate agent images in PDF architecture in your .xcasset catalog. Back you body your project, Xcode will actualize the acclaimed @1x, @2x, and @3x PNG files from the PDF, and iOS will use alone the bare images.7. IBInspectable and IBDesignable will actually let you see abracadabra in your Interface Builder.Let’s say a artist asks you for a button with bound and angled corders. You can do this programmatically:
Tumblr media
Soccer Ball Logo Png - Soccer Ball Vector Png - Free .. | soccer vector png Or set the User Defined Runtime Attributes:What if I acquaint you… You can accept commodity like this:Do you apprehend what has aloof happened? You accept configurable attributes appropriate in your Interface Builder and what’s more, you can see your changes alive in the cartoon after active your app.How to do this? Simply actualize a bracket from UIButton with the afterward cipher (You can additionally actualize a UIButton chic addendum to accomplish this functionality accessible in every button of your project).If you appetite to apprentice added about this, we acclaim you apprehend this post.8. Accommodate Unit testing!Testing is commodity that can be actual adamantine to absorb in your project, distinctively if you booty on a activity that’s already started, but it’s not absurd and it will advice you a lot. We accept some recomendations for you about testing, analysis this out!
Tumblr media
Free Vector Football – Soccer Players Illustration Png . | soccer vector png Do you apperceive added acceptable practices and tips your own? Share them with the apple in the animadversion area below!Read our commodity about Apple TV to apprentice added air-conditioned being about iOS! Seven Reasons Why You Shouldn't Go To Soccer Vector Png On Your Own | Soccer Vector Png - soccer vector png | Allowed to be able to the blog site, in this particular time period We'll show you about keyword. And now, this can be the very first impression:
Tumblr media
Color Boy Playing Soccer, Color Vector, Boy Vector, Soccer .. | soccer vector png Why not consider image preceding? will be that remarkable???. if you feel and so, I'l d explain to you a number of graphic once more below: So, if you like to acquire the awesome photos regarding (Seven Reasons Why You Shouldn't Go To Soccer Vector Png On Your Own | Soccer Vector Png), press save icon to save these pictures to your laptop. These are prepared for obtain, if you like and want to have it, just click save logo on the post, and it will be instantly downloaded in your notebook computer.} At last if you want to get new and recent image related to (Seven Reasons Why You Shouldn't Go To Soccer Vector Png On Your Own | Soccer Vector Png), please follow us on google plus or save this site, we attempt our best to offer you regular up grade with fresh and new images. We do hope you enjoy staying here. For some upgrades and latest information about (Seven Reasons Why You Shouldn't Go To Soccer Vector Png On Your Own | Soccer Vector Png) shots, please kindly follow us on tweets, path, Instagram and google plus, or you mark this page on book mark area, We attempt to give you up-date regularly with all new and fresh photos, like your searching, and find the perfect for you. Here you are at our website, articleabove (Seven Reasons Why You Shouldn't Go To Soccer Vector Png On Your Own | Soccer Vector Png) published .  At this time we are pleased to declare we have found an incrediblyinteresting nicheto be discussed, namely (Seven Reasons Why You Shouldn't Go To Soccer Vector Png On Your Own | Soccer Vector Png) Many people trying to find specifics of(Seven Reasons Why You Shouldn't Go To Soccer Vector Png On Your Own | Soccer Vector Png) and definitely one of these is you, is not it?
Tumblr media
Soccer Athlete, Soccer Vector, Football, Athlete PNG and .. | soccer vector png
Tumblr media
Download Free png Soccer Vector Free Download Vector .. | soccer vector png Read the full article
0 notes
fitnesshealthyoga-blog · 6 years ago
Photo
Tumblr media
New Post has been published on https://fitnesshealthyoga.com/refactoring-with-uistackview/
Refactoring with UIStackView
Written by Nicholas Steppan Meschke, Software Engineer iOS
What awaits you
As developers who care about the user experience of our app, we’ll likely encounter some scenarios that require a view to reflect different states. Sometimes, the state of the view may even impact its size. The code that handles all those transitions can quickly become a mess. In this article, we want to share our approach to implementing such scenarios using UIStackView.
Let’s start with an example
We use a simple app idea to demonstrate how we can achieve that. We already have a demo app that has a simple button, which increases a counter when tapped. The number of taps is displayed on the screen. To make the demo app more interesting, we’ll add functionality to set and track tapping goals!
We can now set goals! Here’s the code that’s responsible for setting up the constraints for the empty tapping goal view.
private func setupEmptyGoal()   // … code left out   NSLayoutConstraint   .activate([emptyGoalTitleLabel.topAnchor.constraint(equalTo: emptyGoalView.topAnchor, constant: 16),     emptyGoalTitleLabel.trailingAnchor.constraint(equalTo: emptyGoalView.trailingAnchor, constant: -16),     emptyGoalTitleLabel.leadingAnchor.constraint(equalTo: emptyGoalView.leadingAnchor, constant: 16)])   NSLayoutConstraint   .activate([optionsStackView.topAnchor.constraint(equalTo: emptyGoalTitleLabel.bottomAnchor, constant: 32),     optionsStackView.trailingAnchor.constraint(equalTo: emptyGoalView.trailingAnchor, constant: -16),     optionsStackView.leadingAnchor.constraint(equalTo: emptyGoalView.leadingAnchor, constant: 16),     optionsStackView.bottomAnchor.constraint(equalTo: emptyGoalView.bottomAnchor, constant: -16)])   NSLayoutConstraint   .activate([emptyGoalView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),     emptyGoalView.bottomAnchor.constraint(equalTo: bottomAnchor),     emptyGoalView.trailingAnchor.constraint(equalTo: trailingAnchor),     emptyGoalView.leadingAnchor.constraint(equalTo: leadingAnchor)])
The code sets the constraints for the view in the EmptyGoal state. Cool, we now have the state where we don’t have a goal, so let’s create one for when a goal is set.
Here’s the code responsible for creating and setting up view for when we have a goal set. Let’s call this state GoalSet.
private func setupGoalView()   // … code left out   NSLayoutConstraint   .activate([goalTitleLabel.topAnchor.constraint(equalTo: goalView.topAnchor, constant: 16),     goalTitleLabel.centerXAnchor.constraint(equalTo: goalView.centerXAnchor)])   NSLayoutConstraint   .activate([progressLabel.topAnchor.constraint(equalTo: goalTitleLabel.bottomAnchor, constant: 16),     progressLabel.centerXAnchor.constraint(equalTo: goalView.centerXAnchor),     progressLabel.bottomAnchor.constraint(equalTo: goalView.bottomAnchor, constant: -16)])   NSLayoutConstraint   .activate([goalView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),     goalView.topAnchor.constraint(equalTo: goalSetLabel.bottomAnchor, constant: 16),     goalView.bottomAnchor.constraint(equalTo: bottomAnchor),     goalView.trailingAnchor.constraint(equalTo: trailingAnchor),     goalView.leadingAnchor.constraint(equalTo: leadingAnchor)])
Awesome, now we can set a tapping goal and track it in our app. However, there’s one issue here: even though the spacing in our view in the GoalSet state should be 16pt, it appears to be a lot more. That’s weird, but let’s keep improving our app by introducing a Success state, for when we achieve our goal!
Here’s our Success state view and below, the code for it:
private func setupSuccessView()   // … code left out   NSLayoutConstraint   .activate([imageView.topAnchor.constraint(equalTo: successView.topAnchor),     imageView.bottomAnchor.constraint(equalTo: successView.bottomAnchor),     imageView.trailingAnchor.constraint(equalTo: successView.trailingAnchor),     imageView.leadingAnchor.constraint(equalTo: successView.leadingAnchor)])   NSLayoutConstraint   .activate([successView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),     successView.topAnchor.constraint(equalTo: goalSetLabel.bottomAnchor, constant: 16),     successView.bottomAnchor.constraint(equalTo: bottomAnchor),     successView.trailingAnchor.constraint(equalTo: trailingAnchor),     successView.leadingAnchor.constraint(equalTo: leadingAnchor)])
But what’s wrong?
Yay, now we have our 3 state views for our clicking goal app! But when we open our app, it looks like this:
Apart from looking really strange and stretched, we have a bunch of constraints that basically do the same thing: constrain the state views to our superView. How can we improve that?
UIStackView to the rescue!
UIStackViews are a really useful tool for laying out a collection of views in either column or row. In our case, we are going to use the UIStackView to group our state views, and it will adapt its size according to what is inside it. Our new code looks like this:
private func setupStackView()   // … code left out   NSLayoutConstraint   .activate([stackView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),     stackView.trailingAnchor.constraint(equalTo: trailingAnchor),     stackView.leadingAnchor.constraint(equalTo: leadingAnchor),     stackView.bottomAnchor.constraint(equalTo: bottomAnchor)]) private func setupSuccessView()   // … code left out   NSLayoutConstraint   .activate([imageView.topAnchor.constraint(equalTo: successView.topAnchor),     imageView.bottomAnchor.constraint(equalTo: successView.bottomAnchor),     imageView.trailingAnchor.constraint(equalTo: successView.trailingAnchor),     imageView.leadingAnchor.constraint(equalTo: successView.leadingAnchor)]) private func setupGoalView()   // … code left out   NSLayoutConstraint   .activate([goalTitleLabel.topAnchor.constraint(equalTo: goalView.topAnchor, constant: 16),     goalTitleLabel.centerXAnchor.constraint(equalTo: goalView.centerXAnchor)])   NSLayoutConstraint   .activate([progressLabel.topAnchor.constraint(equalTo: goalTitleLabel.bottomAnchor, constant: 16),     progressLabel.centerXAnchor.constraint(equalTo: goalView.centerXAnchor),     progressLabel.bottomAnchor.constraint(equalTo: goalView.bottomAnchor, constant: -16)]) private func setupEmptyGoal()   // … code left out   NSLayoutConstraint   .activate([emptyGoalTitleLabel.topAnchor.constraint(equalTo: emptyGoalView.topAnchor, constant: 16),     emptyGoalTitleLabel.trailingAnchor.constraint(equalTo: emptyGoalView.trailingAnchor, constant: -16),     emptyGoalTitleLabel.leadingAnchor.constraint(equalTo: emptyGoalView.leadingAnchor, constant: 16)])   NSLayoutConstraint   .activate([optionsStackView.topAnchor.constraint(equalTo: emptyGoalTitleLabel.bottomAnchor, constant: 32),     optionsStackView.trailingAnchor.constraint(equalTo: emptyGoalView.trailingAnchor, constant: -16),     optionsStackView.leadingAnchor.constraint(equalTo: emptyGoalView.leadingAnchor, constant: 16),     optionsStackView.bottomAnchor.constraint(equalTo: emptyGoalView.bottomAnchor, constant: -16)])
The layouts are now being shown according to their respective constraints, and our code looks simpler and easier to understand. By using the UIStackView, we swapped 3 addSubview calls for 1, and 12 layout constraints for only 4.
Wait, that’s not all!
Right now, we manage our states through 3 methods and to set our view state to the desired state, we need to call all of them, which looks something like this:
showGoal(false) showEmptyGoal(false) showSuccessView(true)
That’s not optimal for maintainability, and understanding the code could obviously be easier. To fix this, we can introduce a new Enum with values that represent our states. Our code should look like this:
enum GoalViewState     case empty, goal(value: GoalSize), success enum GoalSize: Int     case small = 15, medium = 45, big = 100          var text: String         return String(self.rawValue)     
Inside our view, we create a new property “state” of type GoalViewState. It’s responsible for storing and handling our state changes using didSet method. The resulting code looks like this:
var state: GoalViewState = .empty     didSet         updateState(with: state)      private func updateState(with state: GoalViewState)     stackView.arrangedSubviews.forEach $0.isHidden = true     goalSetLabel.isHidden = true              switch state     case .empty:         emptyGoalView.isHidden = false     case .goal(let value):         goalView.isHidden = false         goalSetLabel.isHidden = false         setGoal(value)     case .success:         successView.isHidden = false     
Ok, but what does it do and how does it work? Simple! If we need to change the state of our view, we can call “state = .goal(.small)”, making the code more readable and less error-prone, giving whoever reads it a better idea of what’s going on.
Wrapping up
UIStackViews are a quite powerful tool, but like other tools, they are not a silver bullet for every problem. Our constraint problem could have been fixed using a height constraint in the superview, however, that would require us to manually calculate the view size, which would make maintenance more difficult later on. Using a UIStackView, the size is inferred using its contents. This not only gives us a convenient way to have our views correctly sized, but also is robust enough to deal with other requirements, such as showing two states at once. That’s it for today!
Have you ever used a UIStackView as we described in this article? Let us know in the comment section below!
***
//check Cookie Opt out and User consent if(!getCookie("tp-opt-out")) !function(f,b,e,v,n,t,s)if(f.fbq)return;n=f.fbq=function()n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments);if(!f._fbq)f._fbq=n; n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)(window, document,'script','https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '1594940627485550'); // Insert your pixel ID here. fbq('track', 'PageView');
Source link
0 notes
arthurknopper · 7 years ago
Text
Volume View iOS Tutorial
When using audio in your app sometimes you want the user to change the volume. With the help of the MPVolumeView object you create a builtin Volume control which also has the abilty  to redirect the output to an airplay device. In this tutorial we will play a sound and display the volume controls.This tutorial is made with Xcode 9 and built for iOS 11.
Open Xcode and create a new Single View App.
For product name, use IOS11VolumeViewTutorial and then fill out the Organization Name and Organization Identifier with your customary values. Enter Swift as Language and choose Next.
Some audio is needed to play, so download the music file and add it to the project, make sure "Copy items if needed" is selected
Go to the Storyboard and drag two Buttons to the main view. Set the title of the left button to Play. Select the Button and select the Auto Layout pin button. Pin the Button to the top and left and click "Add 2 Constraints".
Set the title of the right button to Stop. Select the Button and select the Auto Layout pin button. Pin the Button to the top and right and click "Add 2 Constraints".
The Storyboard should look like this.
Select the Assistant Editor and make sure the ViewController.swift is visible. Ctrl and drag from the Play Button to the ViewController class and create the following Action
Ctrl and drag from the Stop Button to the ViewController class and create the following Action
The AVFoundation framework is needed for playing the music file and the MediaPlayer framework to use the MPVolumeView. Go to the ViewController.swift file and import the frameworks
import AVFoundation import MediaPlayer
Inside the ViewController class add the following properties
var audioPlayer = AVAudioPlayer() let wrapperView = UIView(frame: CGRect(x: 30, y: 200, width: 300, height: 20))
Implement the viewDidLoad method
override func viewDidLoad() { super.viewDidLoad() // 1 let path = Bundle.main.path(forResource: "Amorphis - My Enemy", ofType: "mp3") let music = NSURL(fileURLWithPath: path!) as URL // 2 do { try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback) try AVAudioSession.sharedInstance().setActive(true) try audioPlayer = AVAudioPlayer(contentsOf: music) audioPlayer.prepareToPlay() } catch { print("error") } }
The audio file is extracted from the Application's bundle
The audio session category is set to playback and set to active.
Implement the playSound method
   @IBAction func playSound(_ sender: Any) { // 1 audioPlayer.play() // 2 view.backgroundColor = UIColor.clear view.addSubview(wrapperView) // 3 let volumeView = MPVolumeView(frame: wrapperView.bounds) wrapperView.addSubview(volumeView) }
The audio file starts to play
The wrapper view is added as a subview to the main view
The MPVolumeView is added as a subview of the wrapper view.
Implement the stopSound method.
@IBAction func stopSound(_ sender: Any) { audioPlayer.stop() wrapperView.removeFromSuperview() }
The audio is stopped and the wrapperView including the volume view is removed from the superview.
Build and Run the project, select the play button and change the volume and Output source. Note this can only be run on an actual device, since in the iOS Simulator you will only see a black view.
You can download the source code of the IOS11VolumeViewTutorialat the ioscreator repository on Github.
0 notes
iyarpage · 8 years ago
Text
How to Create Your Own Slide-Out Navigation Panel in Swift
Update Note: This tutorial has been updated for iOS 11, Xcode 9, and Swift 4 by Nick Sakaimbo. The original tutorial was written by Tammy Coron.
This tutorial will show you how to build a slide-out navigation panel, which is a popular alternative to using a UINavigationController or a UITabBarController that allows users to slide content on or off screen.
The slide-out navigation panel design pattern lets developers add permanent navigation to their apps without taking up valuable screen real estate. The user can choose to reveal the navigation at any time, while still seeing their current context.
In this tutorial you’ll take a less-is-more approach so you can apply the slide-out navigation panel technique to your own applications with relative ease.
Getting Started
You’re going to build a slide-out navigation panel into a cute kitten and puppy photo browser. To get started, download the starter project for this tutorial. It’s a zip file, so save it to a convenient location and then extract it to get the project.
Next open the project in Xcode and take a look at how it’s organized. The Assets folder contains a couple of asset catalogs of all of the kitten and puppy images that’ll be displayed in the app. Notice too there’s three main view controllers. When the time comes to adapt this tutorial to your own projects, here’s what you should keep in mind:
ContainerViewController: This is where the magic happens! This contains the views of the left, center, and right view controllers and handles things like animations and swiping. In this project, it’s created and added to the window in application(_:didFinishLaunchingWithOptions:) in AppDelegate.swift
CenterViewController: The center panel. You can replace it with your own view controller (make sure you copy the button actions).
SidePanelViewController: Used for the left and right side panels. This could be replaced with your own view controller.
The views for the center, left, and right view controllers are all defined within Main.storyboard, so feel free to take a quick look to get an idea of how the app will look.
Now you’re familiar with the structure of the project, it’s time to start at square one: the center panel.
Finding Your Center
In this section, you’re going to place the CenterViewController inside the ContainerViewController, as a child view controller.
Note: This section uses a concept called View Controller Containment introduced in iOS 5. If you’re new to this concept, check out Chapter 22 in iOS 5 by Tutorials, “UIViewController Containment.”
Open ContainerViewController.swift. At the bottom of the file there’s a small extension for UIStoryboard. It adds a few static methods which make it a bit more concise to load specific view controllers from the app’s storyboard. You’ll make use of these methods soon.
Add a couple of properties to ContainerViewController for the CenterViewController and for a UINavigationController, above viewDidLoad():
var centerNavigationController: UINavigationController! var centerViewController: CenterViewController!
Note: These are implicitly-unwrapped optionals (as denoted by the !). They have to be optional because their values won’t be initialized until after init() has been called, but they can be automatically unwrapped because once they’re created you know they will always have values.
Next, add the following block of code to viewDidLoad(), beneath the call to super:
centerViewController = UIStoryboard.centerViewController() centerViewController.delegate = self // wrap the centerViewController in a navigation controller, so we can push views to it // and display bar button items in the navigation bar centerNavigationController = UINavigationController(rootViewController: centerViewController) view.addSubview(centerNavigationController.view) addChildViewController(centerNavigationController) centerNavigationController.didMove(toParentViewController: self)
The code above creates a new CenterViewController and assigns it to the centerViewController property you just created. It also creates a UINavigationController to contain the center view controller. It then adds the navigation controller’s view to ContainerViewController‘s view and sets up the parent-child relationship using addSubview(_:), addChildViewContoller(_:) and didMove(toParentViewController:).
It also sets the current view controller as the center view controller’s delegate. This will be used by the center view controller to tell its container when to show and hide the left and right side panels.
If you try to build now, you’ll see an error when the code assigns the delegate. You need to modify this class so it implements the CenterViewControllerDelegate protocol. You’ll add an extension to ContainerViewController to implement it. Add the following code above the UIStoryboard extension near the bottom of the file (this also includes a number of empty methods which you’ll fill out later):
// MARK: CenterViewController delegate extension ContainerViewController: CenterViewControllerDelegate { func toggleLeftPanel() { } func toggleRightPanel() { } func addLeftPanelViewController() { } func addRightPanelViewController() { } func animateLeftPanel(shouldExpand: Bool) { } func animateRightPanel(shouldExpand: Bool) { } }
Now is a good time to check your progress. Build and run the project. If all went well, you should see something similar to the screen below:
Yes, those buttons at the top will eventually bring you kitties and puppies. What better reason could there be for creating sliding navigation panels? But to get your cuteness fix, you’ve got to start sliding. First, to the left!
Kittens to the Left of Me…
You’ve created your center panel, but adding the left view controller requires a different set of steps. There’s quite a bit of set up to get through here, so bear with it. Think of the kittens!
To expand the left menu, the user will tap on the Kitties button in the navigation bar. So head on over to CenterViewController.swift.
In the interests of keeping this tutorial focused on the important stuff, the IBActions and IBOutlets are pre-connected for you in the storyboard. However, to implement your DIY slide-out navigation panel, you need to understand how the buttons are configured.
Notice there’s already two IBAction methods, one for each of the buttons. Find kittiesTapped(_:) and add the following implementation to it:
delegate?.toggleLeftPanel?()
As previously mentioned, the method is already hooked up to the Kitties button.
This uses optional chaining to only call toggleLeftPanel() if delegate has a value and it has implemented the method.
You can see the definition of the delegate protocol in CenterViewControllerDelegate.swift. As you’ll see, there’s optional methods toggleLeftPanel() and toggleRightPanel(). If you remember, when you set up the center view controller instance earlier, you set its delegate as the container view controller. Time to go and implement toggleLeftPanel().
Note: For more information on delegate methods and how to implement them, please refer to Apple’s Developer Documentation.
Open ContainerViewController.swift. First add an enum to the ContainerViewController class, right below the class name:
class ContainerViewController: UIViewController { enum SlideOutState { case bothCollapsed case leftPanelExpanded case rightPanelExpanded } // ...
This will let you keep track of the current state of the side panels, so you can tell whether neither panel is visible, or one of the left or right panels are visible.
Next, add two more properties below your existing centerViewController property:
var currentState: SlideOutState = .bothCollapsed var leftViewController: SidePanelViewController?
These will hold the current state, and the left side panel view controller itself:
The current state is initialized to be .bothCollapsed – that is, neither of the side panels are visible when the app first loads. The leftViewController property is an optional, because you’ll be adding and removing the view controller at various times, so it might not always have a value.
Next, add the implementation for the toggleLeftPanel() delegate method:
let notAlreadyExpanded = (currentState != .leftPanelExpanded) if notAlreadyExpanded { addLeftPanelViewController() } animateLeftPanel(shouldExpand: notAlreadyExpanded)
First, this method checks whether the left side panel is already expanded or not. If it’s not already visible, then it adds the panel to the view hierarchy and animates it to its ‘open’ position. If the panel is already visible, then it animates the panel to its ‘closed’ position.
Next, you’ll include the code to add the left panel to the view hierarchy. Locate addLeftPanelViewController(), and add the following code inside it:
guard leftViewController == nil else { return } if let vc = UIStoryboard.leftViewController() { vc.animals = Animal.allCats() addChildSidePanelController(vc) leftViewController = vc }
The code above first checks to see if the leftViewController property is nil. If it is, then creates a new SidePanelViewController, and sets its list of animals to display – in this case, cats!
Next, add the implementation for addChildSidePanelController(_:) below addLeftPanelViewController():
func addChildSidePanelController(_ sidePanelController: SidePanelViewController) { view.insertSubview(sidePanelController.view, at: 0) addChildViewController(sidePanelController) sidePanelController.didMove(toParentViewController: self) }
This method inserts the child view into the container view controller. This is much the same as adding the center view controller earlier. It simply inserts its view (in this case it’s inserted at z-index 0, which means it will be below the center view controller) and adds it as a child view controller.
It’s almost time to try the project out again, but there’s one more thing to do: add some animation! It won’t take long!
And sliiiiiiide!
First, add a constant below your other properties in ContainerViewController.swift:
let centerPanelExpandedOffset: CGFloat = 60
This value is the width, in points, of the center view controller left visible once it has animated offscreen. 60 points should do it.
Next, locate the method stub for animateLeftPanel(shouldExpand:) and add the following block of code to it:
if shouldExpand { currentState = .leftPanelExpanded animateCenterPanelXPosition( targetPosition: centerNavigationController.view.frame.width - centerPanelExpandedOffset) } else { animateCenterPanelXPosition(targetPosition: 0) { finished in self.currentState = .bothCollapsed self.leftViewController?.view.removeFromSuperview() self.leftViewController = nil } }
This method simply checks whether it’s been told to expand or collapse the side panel. If it should expand, then it sets the current state to indicate the left panel is expanded, and then animates the center panel so it’s open. Otherwise, it animates the center panel closed and then removes its view and sets the current state to indicate it’s closed.
Finally, add animateCenterPanelXPosition(targetPosition:completion:) underneath animatedLeftPanel(shouldExpand:):
func animateCenterPanelXPosition(targetPosition: CGFloat, completion: ((Bool) -> Void)? = nil) { UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: { self.centerNavigationController.view.frame.origin.x = targetPosition }, completion: completion) }
This is where the actual animation happens. The center view controller’s view is animated to the specified position, with a nice spring animation. The method also takes an optional completion closure, which it passes on to the UIView animation. You can try tweaking the duration and spring damping parameters if you want to change the appearance of the animation.
OK… It’s taken a little while to get everything in place, but now is a great time to build and run the project. So do it!
When you’ve run the project, try tapping on the Kitties button in the navigation bar. The center view controller should slide over – whoosh! – and reveal the Kitties menu underneath. D’aww, look how cute they all are.
But too much cuteness can be a dangerous thing! Tap the Kitties button again to hide them!
Me and my shadow
When the left panel is open, notice how it’s right up against the center view controller. It would be nice if there were a bit more of a distinction between them. How about adding a shadow?
Still in ContainerViewController.swift, add the following method below your animation methods:
func showShadowForCenterViewController(_ shouldShowShadow: Bool) { if shouldShowShadow { centerNavigationController.view.layer.shadowOpacity = 0.8 } else { centerNavigationController.view.layer.shadowOpacity = 0.0 } }
This adjusts the opacity of the navigation controller’s shadow to make it visible or hidden. You can implement a didSet observer to add or remove the shadow whenever the currentState property changes.
Next, scroll to the top of ContainerViewController.swift and change the currentState declaration to:
var currentState: SlideOutState = .bothCollapsed { didSet { let shouldShowShadow = currentState != .bothCollapsed showShadowForCenterViewController(shouldShowShadow) } }
The didSet closure will be called whenever the property’s value changes. If either of the panels are expanded, then it shows the shadow.
Build and run the project again. This time when you tap the kitties button, check out the sweet new shadow! Looks better, huh?
Up next, adding the same functionality but for the right side, which means… puppies!
Puppies to the Right…
To add the right panel view controller, simply repeat the steps for adding the left view controller.
Open ContainerViewController.swift, and add the following property below the leftViewController property:
var rightViewController: SidePanelViewController?
Next, locate toggleRightPanel(), and add the following implementation:
let notAlreadyExpanded = (currentState != .rightPanelExpanded) if notAlreadyExpanded { addRightPanelViewController() } animateRightPanel(shouldExpand: notAlreadyExpanded)
Next, replace the implementations for addRightPanelViewController() and animateRightPanel(shouldExpand:) with the following:
func addRightPanelViewController() { guard rightViewController == nil else { return } if let vc = UIStoryboard.rightViewController() { vc.animals = Animal.allDogs() addChildSidePanelController(vc) rightViewController = vc } } func animateRightPanel(shouldExpand: Bool) { if shouldExpand { currentState = .rightPanelExpanded animateCenterPanelXPosition( targetPosition: -centerNavigationController.view.frame.width + centerPanelExpandedOffset) } else { animateCenterPanelXPosition(targetPosition: 0) { _ in self.currentState = .bothCollapsed self.rightViewController?.view.removeFromSuperview() self.rightViewController = nil } } }
The code above is almost an exact duplicate of the code for the left panel, except of course for the differences in method and property names and the direction. If you have any questions about it, review the explanation from the previous section.
Just as before, the IBActions and IBOutlets have been connected in the storyboard for you. Similar to the Kitties button, the Puppies button is hooked up to an IBAction method named puppiesTapped(_:). This button controls the sliding of the center panel to reveal the right-side panel.
Finally, switch to CenterViewController.swift and add the following snippet to puppiesTapped(_:):
delegate?.toggleRightPanel?()
Again, this is the same as kittiesTapped(_:), except it’s toggling the right panel instead of the left.
Time to see some puppies!
Build and run the program again to make sure everything is working. Tap on the Puppies button. Your screen should look like this:
Looking good, right? But remember, you don’t want to expose yourself to the cuteness of puppies for too long, so tap that button again to hide them away.
You can now view both kitties and puppies, but it would be great to be able to view a bigger picture of each one, wouldn’t it? MORE CUTENESS :]
Pick An Animal, Any Animal
The kitties and puppies are listed within the left and right panels. These are both instances of SidePanelViewController, which essentially just contain table views.
Head over to SidePanelViewControllerDelegate.swift to take a look at the SidePanelViewController delegate method. A side panel’s delegate can be notified via this method whenever an animal is tapped. Let’s use it!
In SidePanelViewController.swift, first add an optional delegate property at the top of the class, underneath the table view IBOutlet:
var delegate: SidePanelViewControllerDelegate?
Then fill in the implementation for tableView(_:didSelectRowAt:) within the UITableViewDelegate extension:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let animal = animals[indexPath.row] delegate?.didSelectAnimal(animal) }
If there’s a delegate set, this will tell it an animal has been selected. Currently there’s no delegate yet! It would make sense for CenterViewController to be the side panel’s delegate, as it can then display the selected animal photo and title.
Open up CenterViewController.swift to implement the delegate protocol. Add the following extension to the bottom of the file, beneath the existing class definition:
extension CenterViewController: SidePanelViewControllerDelegate { func didSelectAnimal(_ animal: Animal) { imageView.image = animal.image titleLabel.text = animal.title creatorLabel.text = animal.creator delegate?.collapseSidePanels?() } }
This method simply populates the image view and labels in the center view controller with the animal’s image, title, and creator. Then, if the center view controller has a delegate of its own, you can tell it to collapse the side panel away so you can focus on the selected item.
collapseSidePanels() is not implemented yet. Open, ContainerViewController.swift and add the following method below toggleRightPanel():
func collapseSidePanels() { switch currentState { case .rightPanelExpanded: toggleRightPanel() case .leftPanelExpanded: toggleLeftPanel() default: break } }
The switch statement in this method simply checks the current state of the side panels, and collapses whichever one is open (if any!).
Finally, update addChildSidePanelViewController(_:) to the following implementation:
func addChildSidePanelController(_ sidePanelController: SidePanelViewController) { sidePanelController.delegate = centerViewController view.insertSubview(sidePanelController.view, at: 0) addChildViewController(sidePanelController) sidePanelController.didMove(toParentViewController: self) }
In addition to what it was doing previously, the method will now set the center view controller as the side panels’ delegate.
That should do it! Build and run the project again. View kitties or puppies, and tap on one of the cute little critters. The side panel should collapse itself again and you should see the details of the animal you chose.
Move Your Hands Back and Forth
The navigation bar buttons are great, but most apps also allow you to “swipe” to open the side panels. Adding gestures to your app is surprisingly simple. Don’t be intimated; you’ll do fine!
Open ContainerViewController.swift and locate viewDidLoad(). Add the following to the end of the method:
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) centerNavigationController.view.addGestureRecognizer(panGestureRecognizer)
The above code defines a UIPanGestureRecognizer and assigns handlePanGesture(_:) to it to handle any detected pan gestures. (You will write the code for that method in a moment.)
By default, a pan gesture recognizer detects a single touch with a single finger, so it doesn’t need any extra configuration. You just need to add the newly created gesture recognizer to centerNavigationController view.
Note: Refer to our Using UIGestureRecognizer with Swift Tutorial for more information about gesture recognizers in iOS.
Next make this class a UIGestureRecognizerDelegate by adding the following extension at the bottom of the file, above the UIStoryboard extension:
// MARK: Gesture recognizer extension ContainerViewController: UIGestureRecognizerDelegate { @objc func handlePanGesture(_ recognizer: UIPanGestureRecognizer) { } }
Didn’t I tell you it’d be simple? There’s only one move remaining in your slide-out navigation panel routine.
Now Move That View!
The gesture recognizer calls handlePanGesture(_:) when it detects a gesture. So your last task for this tutorial is to implement the method.
Add the following block of code to the method stub you just added above (it’s a big one!):
let gestureIsDraggingFromLeftToRight = (recognizer.velocity(in: view).x > 0) switch recognizer.state { case .began: if currentState == .bothCollapsed { if gestureIsDraggingFromLeftToRight { addLeftPanelViewController() } else { addRightPanelViewController() } showShadowForCenterViewController(true) } case .changed: if let rview = recognizer.view { rview.center.x = rview.center.x + recognizer.translation(in: view).x recognizer.setTranslation(CGPoint.zero, in: view) } case .ended: if let _ = leftViewController, let rview = recognizer.view { // animate the side panel open or closed based on whether the view // has moved more or less than halfway let hasMovedGreaterThanHalfway = rview.center.x > view.bounds.size.width animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway) } else if let _ = rightViewController, let rview = recognizer.view { let hasMovedGreaterThanHalfway = rview.center.x < 0 animateRightPanel(shouldExpand: hasMovedGreaterThanHalfway) } default: break }
The pan gesture recognizer detects pans in any direction, but you're only interested in horizontal movement. First, you set up the gestureIsDraggingFromLeftToRight Boolean to check for this using the x component of the gesture velocity.
There's three states that need to be tracked: UIGestureRecognizerState.began, UIGestureRecognizerState.changed, and UIGestureRecognizerState.ended:
.began: If the user starts panning, and neither panel is visible then shows the correct panel based on the pan direction and makes the shadow visible.
.changed: If the user is already panning, moves the center view controller's view by the amount the user has panned
.ended: When the pan ends, check whether the left or right view controller is visible. Depending on which one is visible and how far the pan has gone, perform the animation.
You can move the center view around, and show and hide the left and right views using a combination of these three states, as well as the location and velocity / direction of the pan gesture.
For example, if the gesture direction is right, then show the left panel. If the direction is left, then show the right panel.
Build and run the program again. At this point, you should be able to slide the center panel left and right, revealing the panels underneath. If everything is working... you're good to go!
Where to Go from Here?
Congratulations! If you made it all the way through, you're a slide-out navigation panel ninja!
I hope you enjoyed this tutorial. Feel free to download the completed project file. I'm sure you'll enjoy being stuck in the middle of kitties and puppies!
If you want to try a pre-built library over the DIY solution, be sure to check out SideMenu. For an in-depth discussion of the origins of this UI control (and a trip down memory lane), check out iOS developer and designer Ken Yarmosh's post New iOS Design Pattern: Slide-Out Navigation. He does a great job of explaining the benefits of using this design pattern and showing common uses in the wild.
Leave a comment in the forums below to share your slide-out moves and grooves!
The post How to Create Your Own Slide-Out Navigation Panel in Swift appeared first on Ray Wenderlich.
How to Create Your Own Slide-Out Navigation Panel in Swift published first on http://ift.tt/2fA8nUr
0 notes
jacob-cs · 7 years ago
Video
youtube
Swift Animations: Awesome Facebook Pop Up! Press and Hold (Ep 1)
my review point is /10
https://youtu.be/wbmTy32s7GQ?t=6m13s   remove status bar
https://youtu.be/wbmTy32s7GQ?t=7m20s   long press gesture recognizer를 설치 
https://youtu.be/wbmTy32s7GQ?t=10m40s   stack view를 통해 여러개의 버튼을 담을 콘테이너 만들기
https://youtu.be/wbmTy32s7GQ?t=12m55s   상황에 따라 ui view를 더하거나 제거하는 방법 (removeFromSuperview, addSubview)
https://youtu.be/wbmTy32s7GQ?t=13m41s   터치가 이���어진 위치를 찾고 그 위치에 ui view를 첨가했다가 사라지게 하는 작업
https://youtu.be/wbmTy32s7GQ?t=18m37s   실제 ui view의 애니메이션 제작 작업
0 notes
jacob-cs · 7 years ago
Video
youtube
my review point is 10/10
NSLayoutConstraint 사용법의 예시 (길이10분정도)
//  ViewController.swift
//  boundsAndFrame
//
//  Created by AJ Norton on 8/27/15.
//  Copyright (c) 2015 AJ Norton. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
 override func viewDidLoad() {
   super.viewDidLoad()
   // Do any additional setup after loading the view, typically from a nib.
   let v1 = UIView(frame: CGRectMake(50, 50, 300, 200))
   v1.backgroundColor = UIColor.redColor()
   self.view.addSubview(v1)
   let v2 = UIView()
   v2.backgroundColor = UIColor.greenColor()
   v2.setTranslatesAutoresizingMaskIntoConstraints(false)
   v1.addSubview(v2)
   v1.addConstraint(
     NSLayoutConstraint(item: v2,
       attribute: NSLayoutAttribute.Left,
     relatedBy: NSLayoutRelation.Equal,
     toItem: v1,
     attribute: .Left,
     multiplier: 1,
     constant: 0))
   v1.addConstraint(
     NSLayoutConstraint(item: v2,
       attribute: NSLayoutAttribute.Right,
       relatedBy: NSLayoutRelation.Equal,
       toItem: v1,
       attribute: .Right,
       multiplier: 1,
       constant: 0))
   v1.addConstraint(
     NSLayoutConstraint(item: v2,
       attribute: NSLayoutAttribute.Top,
       relatedBy: NSLayoutRelation.Equal,
       toItem: v1,
       attribute: .Top,
       multiplier: 1,
       constant: 0))
   v1.addConstraint(
     NSLayoutConstraint(item: v2,
       attribute: NSLayoutAttribute.Height,
       relatedBy: NSLayoutRelation.Equal,
       toItem: v1,
       attribute: .Height,
       multiplier: 0.5,
       constant: 0))
 }
 override func didReceiveMemoryWarning() {
   super.didReceiveMemoryWarning()
   // Dispose of any resources that can be recreated.
 }
}
0 notes
iyarpage · 8 years ago
Text
Google Material Design Tutorial for iOS: Getting Started
Upon reading the title of this tutorial, you may be wondering how the terms “Google Material Design” and “iOS” ended up alongside each other. After all, Material Design is widely known for being the face of Google, and particularly on Android.
It turns out however, that Google has a much broader vision for Material Design that extends across many platforms, including iOS. Google has even gone as far as open-sourcing the components they’ve used to build Material Design-powered apps on iOS.
In this tutorial, you’ll get a primer on Material Design and build a simple app that displays articles from a number of different news sources via the newsapi.org API.
Using Google Material Design Components for iOS, you will beautify the app with a flexible header, standard material colors, typography, sliding tabs, and cards with ink.
Getting Started
Download the starter project for News Ink, and take a look around to familiarize yourself.
You may notice that the project is using CocoaPods. In your Terminal, navigate to the project’s root folder and run pod install.
Note: If you’re not familiar with CocoaPods we have a good introductory tutorial you can read to get familiar with the dependency manager.
Before you start working with the app, you’ll need to obtain a free newsapi.org key by signing up at http://ift.tt/2mXbvsQ.
Once you’ve got your key, open NewsClient.swift and insert your key in the Constants struct like so:
static let apiKey = "REPLACE_WITH_NEWSAPIORG_KEY"
Then build and run.
There’s nothing terribly interesting yet: just a basic list of articles with photo and basic information. You can tap on an item in the list to go to a web view of the full article, but that’s about it.
Before diving into some code, it’s worth learning a little about Material Design.
Material Design
Google introduced Material Design in 2014, and it’s quickly become the UI/UX standard across all of Google’s web and mobile products. The Google Material Design Guidelines is a great place to start, and I’d recommend having a quick read through before you go any further.
But why is Material Design a good idea, and more importantly, why would you want to use it for an iOS app? After all, Apple has its own UI/UX guidelines in the form of the Human Interface Guidelines.
The answer lies in how we use the devices around us. From mobile phones, to tablets, to desktop PCs, to the television; our daily lives are now a journey from one screen to the next. A single interface design that feels the same across all screens and devices makes for a smooth user experience and greatly reduces the cognitive load of jumping from one device to the next.
An example of dos and don’ts from Google’s Material Design Guidelines.
Using a metaphor that humans are already familiar with — material, in this case, paper — makes approaching each new screen somewhat easier. Moreover, when the design guidelines are extremely opinionated, specific, and supported by actual UI components at the platform level, apps built using those design guidelines easily fall in line with each other.
There’s nothing in the Material specification about only applying to Google’s platforms. All of the benefits of a unified design system are as relevant on iOS as they are on any other platform. If you compare Apple’s Human Interface Guidelines to Google’s Material Design Guidelines, you’ll notice that the Material spec is much deeper and more opinionated. In contrast, Apple’s guidelines are not nearly as prescriptive, particularly when it comes to visual aspects such as typography, color and layouts.
Google is so committed to making Material Design a cross platform standard that it’s created a Platform Adaptation guide that walks you through implementing Material in a way that feels at home on any platform.
That was a lot of info up front! Rest assured, none of it was… immaterial. Now you’re going to have some fun working with the Google Material Components for iOS.
Material Design in Practice on iOS
When you’re done with this section, your app will open with a large header, including a full-bleed photo background and large word mark text. As you scroll, the photo will move and fade out, while the word mark label shrinks until the entire header magically morphs into a more traditional navigation bar.
To start, there’s no navigation bar, title, or anything else to tell the user which app they’re using. You’ll fix that that by introducing an app bar with flexible header, hero image, and fluid scroll effects.
Adding an App Bar
The first, and probably coolest Material Design component you’ll add is an App Bar. In this case, you’ll get a lot of bang for your buck, since the App Bar combines three components in one: Flexible Header, Header Stack View, and Navigation Bar. Each of these components is powerful on its own, but as you will see, when combined, you get something really special.
Open HeroHeaderView.swift. To keep things clean, you’re going to build a UIView subclass that contains all the subviews that make up the flexible header, as well as the logic for how those subviews change in relation to the scroll position.
First add the following struct inside the HeroHeaderView class:
struct Constants { static let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height static let minHeight: CGFloat = 44 + statusBarHeight static let maxHeight: CGFloat = 400.0 }
Here you add a number of constants that will be useful as you build out the header view.
statusBarHeight represents the height of the status bar and minHeight and maxHeight represent the minimum (fully collapsed) and maximum (fully expanded) height of the header.
Now add the following properties to HomeHeaderView:
// MARK: Properties let imageView: UIImageView = { let imageView = UIImageView(image: #imageLiteral(resourceName: "img-hero")) imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true return imageView }() let titleLabel: UILabel = { let label = UILabel() label.text = NSLocalizedString("News Ink", comment: "") label.textAlignment = .center label.textColor = .white label.shadowOffset = CGSize(width: 1, height: 1) label.shadowColor = .darkGray return label }()
Nothing too complicated here; you add a UIImageView to house the header’s background and a UILabel that represents the app title word mark.
Next, add the following code to initialize HomeHeaderView, add the subviews, and specify the layout:
// MARK: Init // 1 init() { super.init(frame: .zero) autoresizingMask = [.flexibleWidth, .flexibleHeight] clipsToBounds = true configureView() } // 2 required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: View // 3 func configureView() { backgroundColor = .darkGray addSubview(imageView) addSubview(titleLabel) } // 4 override func layoutSubviews() { super.layoutSubviews() imageView.frame = bounds titleLabel.frame = CGRect( x: 0, y: Constants.statusBarHeight, width: frame.width, height: frame.height - Constants.statusBarHeight) }
There’s a bit more going on here:
Here you add some basic initialization code that sets a resizing mask, configures clipping mode, then calls the configureView method to, well, configure the view. The MDCAppBar and its cohorts don’t support Auto Layout, so for this section of the tutorial, it’s frame math or bust.
This view is only intended for use via code, so here you prevent it from being loaded via XIB or Storyboards.
To configure the view, you set the background color to .darkGray. As the view collapses, the background image will become transparent, leaving this dark gray color to serve as the navigation bar color. You also added the background image and label as subviews.
The layout code here does two things. First, it assures that the background image fills the frame of the header view. Second, it also fills the label to the header frame, but accounts for the status bar height so that the label is vertically centered between the lower edge of the status bar and the bottom edge of the header frame.
Now that you have the basic header view with subviews in place, it’s time to configure the App Bar and use your header view as the content.
Open ArticlesViewController.swift and import the Material Components by adding the following import statement at the top of the file, below the existing imports:
import MaterialComponents
Now add the following property declarations above the existing properties:
let appBar = MDCAppBar() let heroHeaderView = HeroHeaderView()
You have a property for the App Bar (an instance of MDCAppBar) and one for the HeroHeaderView you created in previous steps.
Next, add the following method to the ArticlesViewController extension marked as // MARK: UI Configuration:
func configureAppBar() { // 1 self.addChildViewController(appBar.headerViewController) // 2 appBar.navigationBar.backgroundColor = .clear appBar.navigationBar.title = nil // 3 let headerView = appBar.headerViewController.headerView headerView.backgroundColor = .clear headerView.maximumHeight = HeroHeaderView.Constants.maxHeight headerView.minimumHeight = HeroHeaderView.Constants.minHeight // 4 heroHeaderView.frame = headerView.bounds headerView.insertSubview(heroHeaderView, at: 0) // 5 headerView.trackingScrollView = self.collectionView // 6 appBar.addSubviewsToParent() }
There’s quite a lot going on here, so let’s break it down:
To start, you add the app bar’s header view controller as a child view controller of the ArticlesViewController. This is required so that the header view controller can receive standard UIViewController events.
Next, you configure the background color of the app bar to be clear, since you’ll be relying on the hero header view subclass to provide the color. You also set the titleView property to nil because the hero header view also provides a custom title.
Now you configure the app bar’s flexible header view, first by setting it’s background to .clear, again because your hero header view subclass will handle the background. Then you set the min and max heights to the values you defined in the HeroHeaderView.Constants struct. When the collection view is at scroll position zero (e.g. the top), the app bar will be at max height. As you scroll the content, the app bar will collapse until it reaches min height, where it will stay until the collection view is scrolled back towards the top.
Here you set up the initial frame of the hero header view to match the app bar’s header view, then insert it as the bottom-most subview of the header view. This effectively sets the hero header view as the primary content of the app bar’s flexible header view.
Next, you set the header view’s trackingScrollView to the collection view. The flexible header needs to know which UIScrollView subclass to use for tracking scroll events so that it can adjust its size, position, and adjust its subviews as the user scrolls.
Finally, you call addSubviewsToParent on the app bar as required by MDCAppBar in order to add a few of its views to your view controller’s view.
Now invoke configureAppBar() by adding it to viewDidLoad(), right after calling super.viewDidLoad():
override func viewDidLoad() { super.viewDidLoad() configureAppBar() configureCollectionView() refreshContent() }
Build and run, and you should see the following:
Sweet, the header is there! But there are a few problems.
Flexible header height
First, the title logo’s font is small, and as a result, looks awful. Try scrolling the collection view, and you’ll also notice that the flexible header doesn’t seem so flexible yet.
Both of these problems are tied to the fact that there is still some configuration needed to fully wire up the app bar to the collection view’s scroll events.
It turns out that simply setting the flexible header’s trackingScrollView is not enough. You also have to explicitly inform it of scroll events by passing them via the UIScrollViewDelegate methods.
Add the following to the same UI Configuration extension on ArticlesViewController, below where you added configureAppBar():
// MARK: UIScrollViewDelegate override func scrollViewDidScroll(_ scrollView: UIScrollView) { let headerView = appBar.headerViewController.headerView if scrollView == headerView.trackingScrollView { headerView.trackingScrollDidScroll() } } override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let headerView = appBar.headerViewController.headerView if scrollView == headerView.trackingScrollView { headerView.trackingScrollDidEndDecelerating() } } override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { let headerView = appBar.headerViewController.headerView if scrollView == headerView.trackingScrollView { headerView.trackingScrollDidEndDraggingWillDecelerate(decelerate) } } override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { let headerView = appBar.headerViewController.headerView if scrollView == headerView.trackingScrollView { headerView.trackingScrollWillEndDragging(withVelocity: velocity, targetContentOffset: targetContentOffset) } }
In each of these methods, you check if the scroll view is the one you care about (e.g. the header view’s trackingScrollView), and if it is, pass along the event.
Build and run, and you should now see that the header’s height has become flexible.
Adding more effectst
Now that the flexible header is appropriately tied to the collection view’s scrolling, it’s time to have your HeroHeaderView respond to header scroll position changes in order to create some neat effects.
Open HeroHeaderView.swift once more, and add the following method to HeroHeaderView:
func update(withScrollPhasePercentage scrollPhasePercentage: CGFloat) { // 1 let imageAlpha = min(scrollPhasePercentage.scaled(from: 0...0.8, to: 0...1), 1.0) imageView.alpha = imageAlpha // 2 let fontSize = scrollPhasePercentage.scaled(from: 0...1, to: 22.0...60.0) let font = UIFont(name: "CourierNewPS-BoldMT", size: fontSize) titleLabel.font = font }
This is a short, but very important method.
To start, the method takes a scrollPhase value as its only parameter. The scroll phase is a number from 0.0 to 1.0, where 0.0 is when the flexible header is at minimum height, and 1.0 represents, you guessed it, the header at maximum height.
Through the use of a scaled utility extension in the starter project, the scroll phase is mapped to values appropriate for each of the two header components:
By mapping 0...0.8 to 0...1, the alpha of the background goes from 0 when the header is completely collapsed, to 1.0 once the phase hits 0.8 as it is expanded. This prevents the image from fading away as soon as the user starts scrolling the content.
You map the font size range for the title logo as 22.0...60.0. This means that the title logo will start at font size 60.0 when the header is fully expanded, then shrink as it is collapsed.
To connect the method you just added, open ArticlesViewController.swift once more and add the following extension:
// MARK: MDCFlexibleHeaderViewLayoutDelegate extension ArticlesViewController: MDCFlexibleHeaderViewLayoutDelegate { public func flexibleHeaderViewController(_ flexibleHeaderViewController: MDCFlexibleHeaderViewController, flexibleHeaderViewFrameDidChange flexibleHeaderView: MDCFlexibleHeaderView) { heroHeaderView.update(withScrollPhasePercentage: flexibleHeaderView.scrollPhasePercentage) } }
This passes the header scroll phase event straight to your hero header view by invoking the method you just added to HeroHeaderView.
Last but not least, add the following line to configureAppBar() in order to wire up the header layout delegate:
appBar.headerViewController.layoutDelegate = self
Build and run, and you should see the following:
As you scroll, the header should collapse, fading the background image and shrinking the title logo. The flexible header even applies its own effects to stretch its content if you pull down when the collection view is at the top most content offset.
Next up, you’ll add a Material-style scrolling tab bar to let you choose from different news sources.
Adding a Tab Bar
Being able to see a single list of news articles from CNN is already making this app feel pretty useful, but wouldn’t it be even better if you could choose from a bunch of different news sources? Material Design includes just the right component for presenting such a list: the tab bar.
“But wait!” you cry, “iOS already has its own tab bar component!”
Indeed it does, but in Material Design the tab bar can function both as a bottom-style bar with icons and titles (much like the iOS tab bar), or as part of a flexible header, where tabs appear as a horizontally scrolling list of titles.
The second mode is more suited to a list where you might not know the number of values until runtime, and the titles are dynamic to the extent that you wouldn’t be able to provide a unique icon for each. It sounds like this fits the bill perfectly for your news sources navigation.
Open ArticlesViewController.swift and add the following property for the tab bar:
let tabBar = MDCTabBar()
You’re going to add the tab bar as the app bar’s “bottom bar”, which means it will stick to the bottom of the flexible header so that it’s always visible, regardless whether the header is expanded or collapsed. To do this, add the following method right below configureAppBar():
func configureTabBar() { // 1 tabBar.itemAppearance = .titles // 2 tabBar.items = NewsSource.allValues.enumerated().map { index, source in return UITabBarItem(title: source.title, image: nil, tag: index) } // 3 tabBar.selectedItem = tabBar.items[0] // 4 tabBar.delegate = self // 5 appBar.headerStackView.bottomBar = tabBar }
This doesn’t look too complicated:
First, you set the item appearance to .titles. This causes the tab bar items to only show titles, without icons.
Here you map all of the news sources, represented by the NewsSource enum, into instances of UITabBarItem. Just as in a UITabBar, this is how the individual tabs are defined. You set the tab on the tab bar item as the index of the news source in the list. This is so that later, when you handle the tab bar selection, you’ll know which news source to select for a given tab.
Next, you set the selected item to the first item in the list. This will set the first news source as the selected news source when the app first starts.
You simply set the tab bar’s delegate to self. You’ll implement this delegate in the next section.
Finally, set the tab bar as the header stack view’s bottom bar to make it “stick” to the bottom of the flexible header.
At this point the tab bar can be configured, but you need to actually call this method first. Find viewDidLoad() and call this new method right below configureAppBar():
configureTabBar()
The bar is now configured, but it still won’t do much because you haven’t implemented the delegate methods yet. Implement its delegate by adding the following extension:
// MARK: MDCTabBarDelegate extension ArticlesViewController: MDCTabBarDelegate { func tabBar(_ tabBar: MDCTabBar, didSelect item: UITabBarItem) { refreshContent() } }
This code refreshes the content every time the selected tab changes. This won’t do much unless you update refreshContent() to take the selected tab into account.
Change refreshContent() to look like the following:
func refreshContent() { guard inProgressTask == nil else { inProgressTask?.cancel() inProgressTask = nil return } guard let selectedItem = tabBar.selectedItem else { return } let source = NewsSource.allValues[selectedItem.tag] inProgressTask = apiClient.articles(forSource: source) { [weak self] (articles, error) in self?.inProgressTask = nil if let articles = articles { self?.articles = articles self?.collectionView?.reloadData() } else { self?.showError() } } }
The above code looks similar to that in the starter project — with one key difference. Instead of hard-coding the news source to .cnn, you obtain the selected tab bar item via tabBar.selectedItem. You then grab the corresponding news source enum via the tab bar item’s tag — remember, you set it to the news source index above. Finally, you pass that news source to the API client method that fetches the articles.
You’re almost there! There’s one more thing to do before achieving tab bar nirvana.
When you configured the app bar, you set the absolute minimum and maximum heights. Without changing anything, you haven’t provided any extra room for the tab bar when the app bar is in the collapsed state. Build and run right now, and you’ll see something like the following when you scroll down into the content:
This would look much snazzier if the app bar allotted space for both the title and the tab bar.
Open HeroHeaderView.swift and change the Constants enum to the following:
struct Constants { static let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height static let tabBarHeight: CGFloat = 48.0 static let minHeight: CGFloat = 44 + statusBarHeight + tabBarHeight static let maxHeight: CGFloat = 400.0 }
Here you add a new constant for tabBarHeight and then add it to the minHeight constant. This will make sure there is enough room for both the title and the tab bar when in the collapsed state.
Finally, there’s one last problem to contend with. Since you added a new component to the flexible header, the title will no longer look centered vertically. You can resolve this by changing layoutSubviews() in HeroHeaderView.swift to the following:
override func layoutSubviews() { super.layoutSubviews() imageView.frame = bounds titleLabel.frame = CGRect( x: 0, y: Constants.statusBarHeight, width: frame.width, height: frame.height - Constants.statusBarHeight - Constants.tabBarHeight) }
The only difference is that you’re now subtracting Constants.tabBarHeight when calculating the title label’s height.
This centers the title label vertically between the status bar at the top and the tab bar at the bottom. It’ll look much nicer and will prevent one of those pesky UX designers from throwing a brick through your window while you sleep.
Build and run, and you can now choose from a number of news sources, all while expanding or collapsing the header to your heart’s content.
Now that you’ve done a number on the header and navigation, it’s time to give the content a magnificent material makeover.
Adding Article Cards
One of the core tenets of Material Design is the idea of using material as a metaphor. Cards are an excellent implementation of this metaphor, and are used to group content, indicate hierarchy or structure, and denote interactivity, all through the use of varying levels of elevation and movement.
The individual news items in your app are rather dull. But you’re about to change that and turn each news item into a card with a ripple touch effect.
Open ArticleCell.swift and add the familiar import statement to pull in Material Components:
import MaterialComponents
To give the cell a shadow, add the following code to the bottom of ArticleCell:
override class var layerClass: AnyClass { return MDCShadowLayer.self } var shadowLayer: MDCShadowLayer? { return self.layer as? MDCShadowLayer }
Here you override the UIView class var layerClass in order to force the view’s backing layer to be of type MDCShadowLayer.
This layer lets you set a shadow elevation and will then render a nice-looking shadow. You then expose a convenience variable named shadowLayer so it’s easier to access the shadow layer for configuration purposes.
Now that the shadow layer is in place, add the following code to awakeFromNib():
// 1 shadowLayer?.elevation = MDCShadowElevationCardResting // 2 layer.shouldRasterize = true layer.rasterizationScale = UIScreen.main.scale // 3 clipsToBounds = false imageView.clipsToBounds = true
Taking each commented section in turn:
First, you set the shadow layer’s elevation to MDCShadowElevationCardResting. This is the standard elevation for a card in the “resting” state. There are other elevations that correspond to various types of components and interactions.
Next, you configure the rasterization mode for the view’s layer in order to improve scrolling performance.
Finally, you set clipsToBounds to false on the cell so the shadow can escape the bounds of the cell, and set the clipsToBounds to true for the image view. Because you’re using the .scaleAspectFill mode, this will ensure the image content stays confined to the view.
Build and run once again. You should now see a classy shadow surrounding each piece of content, giving it a very defined card look.
Your app is now looking decidedly more Material. Those cards almost scream “please tap me”, but alas, when you do so, nothing happens to indicate your tap before you’re ushered away to the article detail.
Ripple effect on tap
Material Design has a universal method of indicating interactivity, through the use of an “ink” component that causes a very subtle ripple to occur whenever something is tapped or clicked on.
Let’s pour some ink onto these cards. Add a variable for an MDCInkTouchController to ArticleCell like so:
var inkTouchController: MDCInkTouchController?
The ink touch controller manages an underlying ink view and deals with handling input. The only other thing to do is initialize the ink touch controller and add it to the view.
Add the following code to awakeFromNib():
inkTouchController = MDCInkTouchController(view: self) inkTouchController?.addInkView()
The ink touch controller maintains a weak reference to the view, so don’t worry about causing a retain cycle here.
Build and run, then tap on a card to see ink in action.
And that’s it! You’ve have yourself a fully armed and operational Material Design news app.
Where to Go From Here?
You can download the finished project here.
The Material Design spec is extremely broad, and the iOS library includes many components that are beyond the scope of this tutorial. If you like what you’ve seen in this tutorial, you’re encouraged to give it a read.
Moreover, you can find a complete list of all iOS material design components here. They all include very complete documentation and are a great place to start if you want to incorporate more aspects of Material Design into your next iOS app.
If you have any comments or questions about this tutorial, please join the forum discussion below!
The post Google Material Design Tutorial for iOS: Getting Started appeared first on Ray Wenderlich.
Google Material Design Tutorial for iOS: Getting Started published first on http://ift.tt/2fA8nUr
0 notes
iyarpage · 8 years ago
Text
Custom UIViewController Transitions: Getting Started
Update note: This tutorial has been updated to iOS 11 and Swift 4 by Richard Critz. The original tutorial was written by József Vesza.
iOS delivers some nice view controller transitions — push, pop, cover vertically — for free but it’s great fun to make your own. Custom UIViewController transitions can significantly enhance your users’ experiences and set your app apart from the rest of the pack. If you’ve avoided making your own custom transitions because the process seems too daunting, you’ll find that it’s not nearly as difficult as you might expect.
In this tutorial, you’ll add some custom UIViewController transitions to a small guessing game app. By the time you’ve finished, you’ll have learned:
How the transitioning API is structured.
How to present and dismiss view controllers using custom transitions.
How to build interactive transitions.
Note: The transitions shown in this tutorial make use of UIView animations, so you’ll need a basic working knowledge of them. If you need help, check out our tutorial on iOS Animation for a quick introduction to the topic.
Getting Started
Download the starter project. Build and run the project; you’ll see the following guessing game:
The app presents several cards in a page view controller. Each card shows a description of a pet and tapping a card reveals which pet it describes.
Your job is to guess the pet! Is it a cat, dog or fish? Play with the app and see how well you do.
The navigation logic is already in place but the app currently feels quite bland. You’re going to spice it up with custom transitions.
Exploring the Transitioning API
The transitioning API is a collection of protocols. This allows you to make the best implementation choice for your app: use existing objects or create purpose-built objects to manage your transitions. By the end of this section, you’ll understand the responsibilities of each protocol and the connections between them. The diagram below shows you the main components of the API:
The Pieces of the Puzzle
Although the diagram looks complex, it will feel quite straightforward once you understand how the various parts work together.
Transitioning Delegate
Every view controller can have a transitioningDelegate, an object that conforms to UIViewControllerTransitioningDelegate.
Whenever you present or dismiss a view controller, UIKit asks its transitioning delegate for an animation controller to use. To replace a default animation with your own custom animation, you must implement a transitioning delegate and have it return the appropriate animation controller.
Animation Controller
The animation controller returned by the transitioning delegate is an object that implements UIViewControllerAnimatedTransitioning. It does the “heavy lifting” of implementing the animated transition.
Transitioning Context
The transitioning context object implements UIViewControllerContextTransitioning and plays a vital role in the transitioning process: it encapsulates information about the views and view controllers involved in the transition.
As you can see in the diagram, you don’t implement this protocol yourself. UIKit creates and configures the transitioning context for you and passes it to your animation controller each time a transition occurs.
The Transitioning Process
Here are the steps involved in a presentation transition:
You trigger the transition, either programmatically or via a segue.
UIKit asks the “to” view controller (the view controller to be shown) for its transitioning delegate. If it doesn’t have one, UIKIt uses the standard, built-in transition.
UIKit then asks the transitioning delegate for an animation controller via animationController(forPresented:presenting:source:). If this returns nil, the transition will use the default animation.
UIKit constructs the transitioning context.
UIKit asks the animation controller for the duration of its animation by calling transitionDuration(using:).
UIKit invokes animateTransition(using:) on the the animation controller to perform the animation for the transition.
Finally, the animation controller calls completeTransition(_:) on the transitioning context to indicate that the animation is complete.
The steps for a dismissing transition are nearly identical. In this case, UIKit asks the “from” view controller (the one being dismissed) for its transitioning delegate. The transitioning delegate vends the appropriate animation controller via animationController(forDismissed:).
Creating a Custom Presentation Transition
Time to put your new-found knowledge into practice! Your goal is to implement the following animation:
When the user taps a card, it flips to reveal the second view scaled down to the size of the card.
Following the flip, the view scales to fill the whole screen.
Creating the Animator
You’ll start by creating the animation controller.
From the menu, select File\New\File…, choose iOS\Source\Cocoa Touch Class, and click Next. Name the file FlipPresentAnimationController, make it a subclass of NSObject and set the language to Swift. Click Next and set the Group to Animation Controllers. Click Create.
Animation controllers must conform to UIViewControllerAnimatedTransitioning. Open FlipPresentAnimationController.swift and update the class declaration accordingly.
class FlipPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning { }
Xcode will raise an error complaining that FlipPresentAnimationController does not conform to UIViewControllerAnimatedTransitioning. Click the Fix to add the necessary stub routines.
You’re going to use the frame of the tapped card as a starting point for the animation. Inside the body of the class, add the following code to store this information.
private let originFrame: CGRect init(originFrame: CGRect) { self.originFrame = originFrame }
Next, you must fill in the code for the two stubs you added. Update transitionDuration(using:) as follows:
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 2.0 }
As the name suggests, this method specifies the duration of your transition. Setting it to two seconds will prove useful during development as it leaves enough time to observe the animation.
Add the following to animateTransition(using:):
// 1 guard let fromVC = transitionContext.viewController(forKey: .from), let toVC = transitionContext.viewController(forKey: .to), let snapshot = toVC.view.snapshotView(afterScreenUpdates: true) else { return } // 2 let containerView = transitionContext.containerView let finalFrame = transitionContext.finalFrame(for: toVC) // 3 snapshot.frame = originFrame snapshot.layer.cornerRadius = CardViewController.cardCornerRadius snapshot.layer.masksToBounds = true
Here’s what this does:
Extract a reference to both the view controller being replaced and the one being presented. Make a snapshot of what the screen will look like after the transition.
UIKit encapsulates the entire transition inside a container view to simplify managing both the view hierarchy and the animations. Get a reference to the container view and determine what the final frame of the new view will be.
Configure the snapshot’s frame and drawing so that it exactly matches and covers the card in the “from” view.
Continue adding to the body of animateTransition(using:).
// 1 containerView.addSubview(toVC.view) containerView.addSubview(snapshot) toVC.view.isHidden = true // 2 AnimationHelper.perspectiveTransform(for: containerView) snapshot.layer.transform = AnimationHelper.yRotation(.pi / 2) // 3 let duration = transitionDuration(using: transitionContext)
The container view, as created by UIKit, contains only the “from” view. You must add any other views that will participate in the transition. It’s important to remember that addSubview(_:) puts the new view in front of all others in the view hierarchy so the order in which you add subviews matters.
Add the new “to” view to the view hierarchy and hide it. Place the snapshot in front of it.
Set up the beginning state of the animation by rotating the snapshot 90˚ around its y-axis. This causes it to be edge-on to the viewer and, therefore, not visible when the animation begins.
Get the duration of the animation.
Note: AnimationHelper is a small utility class responsible for adding perspective and rotation transforms to your views. Feel free to have a look at the implementation. If you’re curious about the magic of perspectiveTransform(for:), try commenting out the call after you finish the tutorial.
You now have everything set up; time to animate! Complete the method by adding the following.
// 1 UIView.animateKeyframes( withDuration: duration, delay: 0, options: .calculationModeCubic, animations: { // 2 UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1/3) { fromVC.view.layer.transform = AnimationHelper.yRotation(-.pi / 2) } // 3 UIView.addKeyframe(withRelativeStartTime: 1/3, relativeDuration: 1/3) { snapshot.layer.transform = AnimationHelper.yRotation(0.0) } // 4 UIView.addKeyframe(withRelativeStartTime: 2/3, relativeDuration: 1/3) { snapshot.frame = finalFrame snapshot.layer.cornerRadius = 0 } }, // 5 completion: { _ in toVC.view.isHidden = false snapshot.removeFromSuperview() fromVC.view.layer.transform = CATransform3DIdentity transitionContext.completeTransition(!transitionContext.transitionWasCancelled) })
Here’s the play-by-play of your animation:
You use a standard UIView keyframe animation. The duration of the animation must exactly match the length of the transition.
Start by rotating the “from” view 90˚ around its y-axis to hide it from view.
Next, reveal the snapshot by rotating it back from its edge-on state that you set up above.
Set the frame of the snapshot to fill the screen.
The snapshot now exactly matches the “to” view so it’s finally safe to reveal the real “to” view. Remove the snapshot from the view hierarchy since it’s no longer needed. Next, restore the “from” view to its original state; otherwise, it would be hidden when transitioning back. Calling completeTransition(_:) informs UIKit that the animation is complete. It will ensure the final state is consistent and remove the “from” view from the container.
Your animation controller is now ready to use!
Wiring Up the Animator
UIKit expects a transitioning delegate to vend the animation controller for a transition. To do this, you must first provide an object which conforms to UIViewControllerTransitioningDelegate. In this example, CardViewController will act as the transitioning delegate.
Open CardViewController.swift and add the following extension at the bottom of the file.
extension CardViewController: UIViewControllerTransitioningDelegate { func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return FlipPresentAnimationController(originFrame: cardView.frame) } }
Here you return an instance of your custom animation controller, initialized with the frame of the current card.
The final step is to mark CardViewController as the transitioning delegate. View controllers have a transitioningDelegate property, which UIKit will query to see if it should use a custom transition.
Add the following to the end of prepare(for:sender:) just below the card assignment:
destinationViewController.transitioningDelegate = self
It’s important to note that it is the view controller being presented that is asked for a transitioning delegate, not the view controller doing the presenting!
Build and run your project. Tap on a card and you should see the following:
And there you have it — your first custom transition!
Dismissing the View Controller
You have a great presentation transition but that’s only half the job. You’re still using the default dismissal transition. Time to fix that!
From the menu, select File\New\File…, choose iOS\Source\Cocoa Touch Class, and click Next. Name the file FlipDismissAnimationController, make it a subclass of NSObject and set the language to Swift. Click Next and set the Group to Animation Controllers. Click Create.
Replace the class definition with the following.
class FlipDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning { private let destinationFrame: CGRect init(destinationFrame: CGRect) { self.destinationFrame = destinationFrame } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.6 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { } }
This animation controller’s job is to reverse the presenting animation so that the UI feels symmetric. To do this it must:
Shrink the displayed view to the size of the card; destinationFrame holds this value.
Flip the view around to reveal the original card.
Add the following lines to animateTransition(using:).
// 1 guard let fromVC = transitionContext.viewController(forKey: .from), let toVC = transitionContext.viewController(forKey: .to), let snapshot = fromVC.view.snapshotView(afterScreenUpdates: false) else { return } snapshot.layer.cornerRadius = CardViewController.cardCornerRadius snapshot.layer.masksToBounds = true // 2 let containerView = transitionContext.containerView containerView.insertSubview(toVC.view, at: 0) containerView.addSubview(snapshot) fromVC.view.isHidden = true // 3 AnimationHelper.perspectiveTransform(for: containerView) toVC.view.layer.transform = AnimationHelper.yRotation(-.pi / 2) let duration = transitionDuration(using: transitionContext)
This should all look familiar. Here are the important differences:
This time it’s the “from” view you must manipulate so you take a snapshot of that.
Again, the ordering of layers is important. From back to front, they must be in the order: “to” view, “from” view, snapshot view. While it may not seem important in this particular transition, it is vital in others, particularly if the transition can be cancelled.
Rotate the “to” view to be edge-on so that it isn’t immediately revealed when you rotate the snapshot.
All that’s needed now is the actual animation itself. Add the following code to the end of animateTransition(using:).
UIView.animateKeyframes( withDuration: duration, delay: 0, options: .calculationModeCubic, animations: { // 1 UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1/3) { snapshot.frame = self.destinationFrame } UIView.addKeyframe(withRelativeStartTime: 1/3, relativeDuration: 1/3) { snapshot.layer.transform = AnimationHelper.yRotation(.pi / 2) } UIView.addKeyframe(withRelativeStartTime: 2/3, relativeDuration: 1/3) { toVC.view.layer.transform = AnimationHelper.yRotation(0.0) } }, // 2 completion: { _ in fromVC.view.isHidden = false snapshot.removeFromSuperview() if transitionContext.transitionWasCancelled { toVC.view.removeFromSuperview() } transitionContext.completeTransition(!transitionContext.transitionWasCancelled) })
This is exactly the inverse of the presenting animation.
First, scale the snapshot view down, then hide it by rotating it 90˚. Next, reveal the “to” view by rotating it back from its edge-on position.
Clean up your changes to the view hierarchy by removing the snapshot and restoring the state of the “from” view. If the transition was cancelled — it isn’t yet possible for this transition, but you will make it possible shortly — it’s important to remove everything you added to the view hierarchy before declaring the transition complete.
Remember that it’s up to the transitioning delegate to vend this animation controller when the pet picture is dismissed. Open CardViewController.swift and add the following method to the UIViewControllerTransitioningDelegate extension.
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { guard let _ = dismissed as? RevealViewController else { return nil } return FlipDismissAnimationController(destinationFrame: cardView.frame) }
This ensures that the view controller being dismissed is of the expected type and then creates the animation controller giving it the correct frame for the card it will reveal.
It’s no longer necessary to have the presentation animation run slowly. Open FlipPresentAnimationController.swift and change the duration from 2.0 to 0.6 so that it matches your new dismissal animation.
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.6 }
Build and run. Play with the app to see your fancy new animated transitions.
Making It Interactive
Your custom animations look really sharp. But, you can improve your app even further by adding user interaction to the dismissal transition. The Settings app in iOS has a great example of an interactive transition animation:
Your task in this section is to navigate back to the card’s face-down state with a swipe from the left edge of the screen. The progress of the transition will follow the user’s finger.
How Interactive Transitions Work
An interaction controller responds either to touch events or programmatic input by speeding up, slowing down, or even reversing the progress of a transition. In order to enable interactive transitions, the transitioning delegate must provide an interaction controller. This can be any object that implements UIViewControllerInteractiveTransitioning.
You’ve already made the transition animation. The interaction controller will manage this animation in response to gestures rather than letting it play like a video. Apple provides the ready-made UIPercentDrivenInteractiveTransition class, which is a concrete interaction controller implementation. You’ll use this class to make your transition interactive.
From the menu, select File\New\File…, choose iOS\Source\Cocoa Touch Class, and click Next. Name the file SwipeInteractionController, make it a subclass of UIPercentDrivenInteractiveTransition and set the language to Swift. Click Next and set the Group to Interaction Controllers. Click Create.
Add the following to the class.
var interactionInProgress = false private var shouldCompleteTransition = false private weak var viewController: UIViewController! init(viewController: UIViewController) { super.init() self.viewController = viewController prepareGestureRecognizer(in: viewController.view) }
These declarations are fairly straightforward.
interactionInProgress, as the name suggests, indicates whether an interaction is already happening.
shouldCompleteTransition will be used internally to control the transition. You’ll see how shortly.
viewController is a reference to the view controller to which this interaction controller is attached.
Next, set up the gesture recognizer by adding the following method to the class.
private func prepareGestureRecognizer(in view: UIView) { let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleGesture(_:))) gesture.edges = .left view.addGestureRecognizer(gesture) }
The gesture recognizer is configured to trigger when the user swipes from the left edge of the screen and is added to the view.
The final piece of the interaction controller is handleGesture(_:). Add that to the class now.
@objc func handleGesture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) { // 1 let translation = gestureRecognizer.translation(in: gestureRecognizer.view!.superview!) var progress = (translation.x / 200) progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0)) switch gestureRecognizer.state { // 2 case .began: interactionInProgress = true viewController.dismiss(animated: true, completion: nil) // 3 case .changed: shouldCompleteTransition = progress > 0.5 update(progress) // 4 case .cancelled: interactionInProgress = false cancel() // 5 case .ended: interactionInProgress = false if shouldCompleteTransition { finish() } else { cancel() } default: break } }
Here’s the play-by-play:
You start by declaring local variables to track the progress of the swipe. You fetch the translation in the view and calculate the progress. A swipe of 200 or more points will be considered enough to complete the transition.
When the gesture starts, you set interactionInProgress to true and trigger the dismissal of the view controller.
While the gesture is moving, you continuously call update(_:). This is a method on UIPercentDrivenInteractiveTransition which moves the transition along by the percentage amount you pass in.
If the gesture is cancelled, you update interactionInProgress and roll back the transition.
Once the gesture has ended, you use the current progress of the transition to decide whether to cancel() it or finish() it for the user.
Now, you must add the plumbing to actually create your SwipeInteractionController. Open RevealViewController.swift and add the following property.
var swipeInteractionController: SwipeInteractionController?
Next, add the following to the end of viewDidLoad().
swipeInteractionController = SwipeInteractionController(viewController: self)
When the picture view of the pet card is displayed, an interaction controller is created and connected to it.
Open FlipDismissAnimationController.swift and add the following property after the declaration for destinationFrame.
let interactionController: SwipeInteractionController?
Replace init(destinationFrame:) with:
init(destinationFrame: CGRect, interactionController: SwipeInteractionController?) { self.destinationFrame = destinationFrame self.interactionController = interactionController }
The animation controller needs a reference to the interaction controller so it can partner with it.
Open CardViewController.swift and replace animationController(forDismissed:) with:
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { guard let revealVC = dismissed as? RevealViewController else { return nil } return FlipDismissAnimationController(destinationFrame: cardView.frame, interactionController: revealVC.swipeInteractionController) }
This simply updates the creation of FlipDismissAnimationController to match the new initializer.
Finally, UIKit queries the transitioning delegate for an interaction controller by calling interactionControllerForDismissal(using:). Add the following method at the end of the UIViewControllerTransitioningDelegate extension.
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { guard let animator = animator as? FlipDismissAnimationController, let interactionController = animator.interactionController, interactionController.interactionInProgress else { return nil } return interactionController }
This checks first that the animation controller involved is a FlipDismissAnimationController. If so, it gets a reference to the associated interaction controller and verifies that a user interaction is in progress. If any of these conditions are not met, it returns nil so that the transition will proceed without interactivity. Otherwise, it hands the interaction controller back to UIKit so that it can manage the transition.
Build and run. Tap a card, then swipe from the left edge of the screen to see the final result.
Congratulations! You’ve created a interesting and engaging interactive transition!
Where to Go From Here?
You can download the completed project for this tutorial here.
To learn more about the kinds of animations you can do, check out Chapter 17, “Presentation Controller & Orientation Animations” in iOS Animations by Tutorials.
This tutorial focuses on modal presentation and dismissal transitions. It’s important to point out that custom UIViewController transitions can also be used when using container view controllers:
When using a navigation controller, vending the animation controllers is the responsibility of its delegate, which is an object conforming to UINavigationControllerDelegate. The delegate can provide an animation controller in navigationController(_:animationControllerFor:from:to:).
A tab bar controller relies on an object implementing UITabBarControllerDelegate to return the animation controller in tabBarController(_:animationControllerForTransitionFrom:to:).
I hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!
The post Custom UIViewController Transitions: Getting Started appeared first on Ray Wenderlich.
Custom UIViewController Transitions: Getting Started published first on http://ift.tt/2fA8nUr
0 notes
iyarpage · 8 years ago
Text
How To Implement A Circular Image Loader Animation with CAShapeLayer
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.
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:
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:
Circular progress. First, you’ll draw a circular progress indicator and update it based on the progress of the download.
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:
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:
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.
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:
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:
You clear the view’s background color so the image behind the view isn’t hidden anymore, and you set progress to 1.
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.
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:
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:
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:
You set the initial values of lineWidth and path to match the current values of the layer.
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.
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.
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:
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:
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.
How To Implement A Circular Image Loader Animation with CAShapeLayer published first on http://ift.tt/2fA8nUr
0 notes
arthurknopper · 8 years ago
Text
Blur Effect iOS Tutorial
The UIVisualEffectView class can be used to apply visual effects to a view. In this tutorial a darkened blur effect will be applied to an image. This tutorial is made with Xcode 8.3.2 and built for iOS 10.3
Project Setup
Open Xcode and create a new Single View Application.
For product name, use iOS10BlurEffectTutorial and then fill out the Organization Name and Organization Identifier with your customary values. Enter Swift as Language and make sure only iPhone is selected in Devices.
For this tutorial we need a image to apply the blur effect to. Download the zip file containing the image. Extract it and add the file to the project. Make sure you check the "Copy items if needed" checkbox.
Go to the Storyboard.  Drag an Image View to the Storyboard. Go to the Size Inspector and make the Height and Width 256 points.
Select the Image View and select the Auto Layout align button. Select the "Horizontally in Container" checkbox and click "Add 1 Constraint".
Select the Image View and select the Auto Layout pin button. Pin the label to the top and select the Width and Height checkboxes. Click "Add 3 Constraints".
Go to the Attributes Inspector and in the ImageView section choose the image file at the Image field.
Drag a Button to the Storyboard and place it below the Image View. Give the button a title of "Blur".  Select the button and Ctrl + drag to the Image View. Hold down the Ctrl button and select "Vertical Spacing" and "Center Horizontally".
The Storyboard should look like this.
Open the Assistant Editor and make sure the ViewController.swift file is visible. Ctrl + drag from the Image View to the ViewController class and create the following Outlet.
Ctrl + drag from the Button to the ViewController class and create the following Action.
Go to the ViewController.swift file. Change the blurImage Action method to
@IBAction func blurImage(_ sender: Any) { // 1 let darkBlur = UIBlurEffect(style: UIBlurEffectStyle.dark) // 2 let blurView = UIVisualEffectView(effect: darkBlur) blurView.frame = imageView.bounds // 3 imageView.addSubview(blurView) }
the dark blur effect is assigned to the darkBlur variable.
The VisualEffectView is created containing the darkened blur effect.
the blurView is added as a subview of the Image View.
Build and Run the project, click the Blur button and the darkened blur affect will be applied to the image.
You can download the source code of the iOS10BlurEffectTutorial at the ioscreator repository on Github.
0 notes
arthurknopper · 8 years ago
Text
Collision Detection with UIKit Dynamics iOS Tutorial
With UIKit Dynamics you can specify collision behaviours to your objects. The dynamic items can collide with each other and/or any boundary you specify. In this tutorial we will create some custom boundaries and randomly let some squares fall down on to these boundaries. This tutorial is made with Xcode 8.3 and built for iOS 10.3
Project Setup
Open Xcode and create a new Single View Application.
For product name, use iOS10CollisionDectectionTutorial and then fill out the Organization Name and Organization Identifier with your customary values. Enter Swift as Language and make sure only iPhone is selected in Devices.
To draw some lines a custom UIView will be used, where the drawRect method will be altered.. Select File -> New -> File -> iOS -> Source -> Cocoa Touch Class. Name the class LineView and make it a subclass of UIView. 
Go to the LineView.swift file.To draw the lines we will create a helper method drawLineFromPoint(fromX:toPoint:pointY:)
func drawLineFromPoint(fromX: CGFloat, toPoint toX: CGFloat, pointY y: CGFloat) { let currentContext = UIGraphicsGetCurrentContext() if let currentContext = currentContext { currentContext.setLineWidth(5.0) currentContext.move(to: CGPoint(x: fromX, y: y)) currentContext.addLine(to: CGPoint(x: toX, y: y)) currentContext.strokePath(); }}
The lines will be drawn with a width of 5 points. Next, we will alter the drawRect method
override func draw(_ rect: CGRect) { drawLineFromPoint(fromX: 0, toPoint: bounds.size.width/3, pointY: bounds.size.height - 100.0) drawLineFromPoint(fromX: bounds.size.width/3, toPoint:bounds.size.width*0.67, pointY:bounds.size.height - 150.0) drawLineFromPoint(fromX: bounds.size.width*0.67, toPoint:bounds.size.width, pointY:bounds.size.height - 100.0)}
We make three calls to our helper function creating three lines of the same width with different heights. We need to connect our MyView to the view controller. Go to the storyboard and select the view. Go to the Identity Inspector and change the class to MyView.
Build and Run the project, the Lines are drawn on the screen.
Next, drag a button to the Storyboard and give it a title of "Next". Select the Buttonl and select the Auto Layout align button. Select the "Horizontally in Container" checkbox and click "Add 1 Constraint".
Select the Button and select the Auto Layout pin button. Pin the label to the top and and click "Add 1 Constraint".
The main view should look like this.
Select the Assistant Editor and make sure the ViewController.swift is visible. Ctrl and drag from the Next Button to the ViewController class  and create the following Action.
 In ViewController.swift some properties needs to be declared to keep track of the views. Add the following properties
var squareViews:[UIView] = [] var animator:UIDynamicAnimator! var colors:[UIColor] = [] var centerPoint:[CGPoint] = [] var sizeOfSquare:CGSize!
The squareViews propery will contain our views. We need the colors array, the centerPoint array and the sizeOfSquare property  to allocate them to our views later on. The animator property is needed to dynamically allocate our behaviours later. Next add the following properties
var leftBoundaryHeight:CGFloat! var middleBoundaryHeight:CGFloat! var rightBoundaryHeight:CGFloat! var leftBoundaryWidth:CGFloat! var middleBoundaryWidth:CGFloat! var leftSquareCenterPointX:CGFloat! var middleSquareCenterPointX:CGFloat! var rightSquareCenterPointX:CGFloat! var squareCenterPointY:CGFloat!
These properties are needed to set our custom boundaries and add a starting point for our squares. First, create a helper method setBoundaryValues to set these properties.
func setBoundaryValues() { leftBoundaryHeight = view.bounds.size.height - 100.0 middleBoundaryHeight = view.bounds.size.height - 150.0 rightBoundaryHeight = view.bounds.size.height - 100.0 leftBoundaryWidth = view.bounds.size.width/3 middleBoundaryWidth = view.bounds.size.width * 0.67 leftSquareCenterPointX = view.bounds.size.width/6 middleSquareCenterPointX = view.bounds.size.width/2 rightSquareCenterPointX = view.bounds.size.width * 0.84 squareCenterPointY = view.bounds.size.height - 400 }
In viewDidLoad, we will call our helper method and set the remainder of the square values.
override func viewDidLoad() { super.viewDidLoad() setBoundaryValues() // Create the colors array colors = [UIColor.red, UIColor.blue, UIColor.green, UIColor.purple, UIColor.gray] // Create the centerpoint of our squares let leftCenterPoint = CGPoint(x: leftSquareCenterPointX, y: squareCenterPointY) let middleCenterPoint = CGPoint(x: middleSquareCenterPointX, y: squareCenterPointY) let rightCenterPoint = CGPoint(x:rightSquareCenterPointX, y: squareCenterPointY) centerPoint = [leftCenterPoint,middleCenterPoint,rightCenterPoint] // set the size of our squares sizeOfSquare = CGSize(width: 50.0, height: 50.0) }
So, every view has a size of 50 and can be of 5 different colors. The main flow of our project happens in our releaseNextSquare(sender:) IBAction method. 
@IBAction func releaseSquare(_ sender: Any) { let newView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: sizeOfSquare.width, height: sizeOfSquare.height)) let randomColorIndex = Int(arc4random()%5) newView.backgroundColor = colors[randomColorIndex] let randomCenterPoint = Int(arc4random()%3) newView.center = centerPoint[randomCenterPoint] squareViews.append(newView) view.addSubview(newView) }
The view is created and a random centerpoint and color is assigned. The new view is added to the main view and to our array. Add the remainder of the lines to the end of the releaseSquare(sender:) action method
animator = UIDynamicAnimator(referenceView: view) // create gravity let gravity = UIGravityBehavior(items: squareViews) animator.addBehavior(gravity) // create collision detection let collision = UICollisionBehavior(items: squareViews) // set collision boundaries collision.addBoundary(withIdentifier: "leftBoundary" as NSCopying, from: CGPoint(x: 0.0,y: leftBoundaryHeight), to: CGPoint(x: leftBoundaryWidth, y: leftBoundaryHeight)) collision.addBoundary(withIdentifier: "middleBoundary" as NSCopying, from: CGPoint(x: view.bounds.size.width/3,y: middleBoundaryHeight), to: CGPoint(x: middleBoundaryWidth, y: middleBoundaryHeight)) collision.addBoundary(withIdentifier: "rightBoundary" as NSCopying, from: CGPoint(x: middleBoundaryWidth,y: rightBoundaryHeight), to: CGPoint(x: view.bounds.size.width, y: rightBoundaryHeight)) collision.collisionMode = .everything animator.addBehavior(collision)
First, we add a gravity behavior to our animator to let our squares fall. Next, a collision behaviour is added with our custom boundaries. This behaviour is also added to our animator. The default collision mode of UICollisionBehaviour is UICollisionBehaviourMode.everything, which means that all items and boundary can collide with each other. Build and Run, and keep pressing the Next button to let the squares fall down.
You can download the source code of the iOS10CollisionDectectionTutorial at the ioscreator repository on Github.
0 notes