Showing posts with label uiviewcontroller. Show all posts
Showing posts with label uiviewcontroller. Show all posts

Tuesday, August 21, 2018

Auto Layout constraint breaks when set active

Leave a Comment

I have a set of AL constraints positioning a child vc that has two positions, expanded and collapsed.

I found that when I add the collapsed constraint, a top anchor to bottom anchor constraint with a constant, when the vc is first created, there seems to be additional spacing when I activate it. Seemingly because the actual height isn't available at the time.

When I add the constraint in viewDidLayoutSubviews there additional spacing is gone and the constraint behaves properly. Except the issue that now when I switch between the constraints in an animation, I cannot deactivate the collapsed constraint as I switch to the expanded constraint and the constraint breaks. Possibly because viewDidLayoutSubviews is called throughout the transition animation.

Here's an abstract of vc setup.

var foregroundExpandedConstraint: NSLayoutConstraint! var foregroundCollapsedConstraint: NSLayoutConstraint!  var foregroundViewController: UIViewController? {     didSet {          setupforegroundViewController(foregroundViewController: foregroundViewController!)     } }  func setupforegroundViewController(foregroundViewController: UIViewController) {      addChildViewController(foregroundViewController)     foregroundViewController.didMove(toParentViewController: self)      guard let foregroundView = foregroundViewController.view else { return }     foregroundView.translatesAutoresizingMaskIntoConstraints = false     view.addSubview(foregroundView)      foregroundExpandedConstraint = foregroundView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15)      let height =  view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15     let cellHeight = ((height) / 6)             foregroundCollapsedConstraint = NSLayoutConstraint(item: foregroundView, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50)      let foregroundViewControllerViewConstraints = [         foregroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),         foregroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),         foregroundView.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor, constant: -50 - 15),         foregroundExpandedConstraint!         ]       NSLayoutConstraint.activate(foregroundViewControllerViewConstraints) } 

And here the animations are preformed using UIViewPropertyAnimator.

func animateTransitionIfNeeded(state: ForegroundState, duration: TimeInterval) {      let containerFrameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) {         [unowned self] in          switch state {         case .expanded:             self.foregroundCollapsedConstraint?.isActive = false             self.foregroundExpandedConstraint?.isActive = true             self.view.layoutIfNeeded()         case .collapsed:             self.foregroundExpandedConstraint?.isActive = false             self.foregroundCollapsedConstraint?.isActive = true             self.view.layoutIfNeeded()         }     }      containerFrameAnimator.addCompletion {  [weak self] (position) in          if position == .start {             switch state {             case .collapsed:                 self?.foregroundCollapsedConstraint?.isActive = false                 self?.foregroundExpandedConstraint?.isActive = true                 self?.foregroundIsExpanded = true                 self?.view.layoutIfNeeded()             case .expanded:                 self?.foregroundExpandedConstraint?.isActive = false                 self?.foregroundCollapsedConstraint?.isActive = true                 self?.foregroundIsExpanded = false                 self?.view.layoutIfNeeded()             }         } else if position == .end {             switch state {             case .collapsed:                 self?.foregroundExpandedConstraint?.isActive = false                 self?.foregroundCollapsedConstraint?.isActive = true                 self?.foregroundIsExpanded = false             case .expanded:                 self?.foregroundExpandedConstraint?.isActive = false                 self?.foregroundCollapsedConstraint?.isActive = true                 self?.foregroundIsExpanded = true             }         }         self?.runningAnimations.removeAll()     } 

Again to reiterate, when I use the following code, setting the constraint as the vc is added to the view hierarchy, it doesn't layout properly. Checking the constraints I see they change after view did layout subviews is called. Each constraint changes appropriately except for the collapsed constraint.

When I add the collapsed constraint in view did layout subviews it behaves properly however I am unable to deactivate it going forwards and the constraint breaks.

override func viewDidLayoutSubviews() {     super.viewDidLayoutSubviews()      let height =  view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15     let cellHeight = ((height) / 6)      if let v = foregroundViewController?.view {         foregroundCollapsedConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50)     } } 

Edit: I've created a repo demonstrating the issue: https://github.com/louiss98/UIViewPropertyAnimator-Layout-Test

Any suggestions?

1 Answers

Answers 1

You can eliminate the "broken" constraint by changing the constant instead of creating a new constraint.

In your viewDidLayoutSubviews() func,

change:

override func viewDidLayoutSubviews() {     super.viewDidLayoutSubviews()      let height =  view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15     let cellHeight = ((height) / 6)      foregroundCollapsedConstraint = NSLayoutConstraint(item: testViewController.view, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50) } 

to:

override func viewDidLayoutSubviews() {     super.viewDidLayoutSubviews()      let height =  view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15     let cellHeight = ((height) / 6)      foregroundCollapsedConstraint.constant = (-cellHeight) * 2 - 50 } 
Read More

Wednesday, October 25, 2017

How do I transition/animate color of UINavigationBar?

Leave a Comment

I have been searching for how to transition/animate the barTintColor of a UINavigationBar for a while now, and I only see different answers. Some use UIView.animateWithDuration, some use CATransition, but the most interesting ones, like this one use animate(alongsideTransition animation.., which I like the sound of, but I can't get it working properly. Am I doing something wrong?

Many specify that I can simply use the transitionCoordinator in viewWillAppear:. I have set up a fresh super tiny project like this:

class RootViewController:UIViewController{ //Only subclassed     override func viewWillAppear(_ animated: Bool) {         super.viewWillAppear(animated)         transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in             self?.setNavigationColors()             }, completion: nil)     }     func setNavigationColors(){         //Override in subclasses     } }  class FirstViewController: RootViewController {     override func viewDidLoad() {         super.viewDidLoad()         self.title = "First"     }     override func setNavigationColors(){         navigationController?.navigationBar.barTintColor = UIColor.white         navigationController?.navigationBar.tintColor = UIColor.black         navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.black]         navigationController?.navigationBar.barStyle = UIBarStyle.default     } } class SecondViewController: RootViewController {     override func viewDidLoad() {         super.viewDidLoad()         self.title = "Second"     }     override func setNavigationColors(){         navigationController?.navigationBar.barTintColor = UIColor.black         navigationController?.navigationBar.tintColor = UIColor.white         navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]         navigationController?.navigationBar.barStyle = UIBarStyle.black     } } 

With this code, this happens: First

  • The push-transition from First to Second looks perfect. All elements transition perfectly, maybe except the StatusBar, which instantly changes to white. I'd rather know how to transition it, but I'll accept it for now.
  • The pop-transition from Second to First is completely wrong. It keeps the colors from Second until the transition is completely done.
  • The drag-transition from Second to First looks alright, when dragging all the way over. Again, the StatusBar instantly becomes black as soon as I start dragging, but I don't know if that's possible to fix.
  • The drag-transition from Second to First but cancelled mid-drag and returning to Second is completely screwed up. It looks fine until Second is completely back in control, and then it suddenly changes itself to First-colors. This should not happen.

I made a few changes to my RootViewController to make it a little better. I removed viewWillAppear: completely, and changed it with this:

class RootViewController:UIViewController{      override func willMove(toParentViewController parent: UIViewController?) {         if let last = self.navigationController?.viewControllers.last as? RootViewController{             if last == self && self.navigationController!.viewControllers.count > 1{                 if let parent = self.navigationController!.viewControllers[self.navigationController!.viewControllers.count - 2] as? RootViewController{                     parent.setNavigationColors()                 }             }         }     }     override func viewWillDisappear(_ animated: Bool) {         if let parent = navigationController?.viewControllers.last as? RootViewController{             parent.animateNavigationColors()         }     }     override func viewDidAppear(_ animated: Bool) {         self.setNavigationColors()     }      func animateNavigationColors(){         transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in             self?.setNavigationColors()             }, completion: nil)     }     func setNavigationColors(){         //Override in subclasses     } } 

With this updated code, I get this: Second

A few observations:

  • The transition from First to Second is the same
  • The pop-transition from Second to First is now animating correctly, except from the back-arrow, the back-text (and the statusBar, but yeah..). These are instantly changed to black. In the first gif, you could see that the back-arrow and the back-text also transitioned.
  • The drag-transition from Second to First also has this problem, the back-arrow and back-text are suddenly instantly black when starting. The barTint is fixed so that it doesn't get the wrong color when cancelling the drag.

What am I doing wrong? How am I supposed to do this?

What I want is to transition all elements smoothly. The tint of the back-button, the back-text, the title, the barTint, and the statusBar. Is this not possible?

3 Answers

Answers 1

You can overwrite the push and pop methods of UINavigationController to set the bar color. I've stored the bar color corresponding to a view controller in its navigation item with a custom subclass of UINavigationItem. The following code works for me in iOS 11 for full and for interactive transitions as well:

import UIKit  class NavigationItem: UINavigationItem {     @IBInspectable public var barTintColor: UIColor? }  class NavigationController: UINavigationController, UIGestureRecognizerDelegate {     func applyTint(_ navigationItem: UINavigationItem?) {         if let item = navigationItem as? NavigationItem {             self.navigationBar.barTintColor = item.barTintColor         }     }      override func viewWillAppear(_ animated: Bool) {         super.viewWillAppear(animated)         applyTint(self.topViewController?.navigationItem)         self.interactivePopGestureRecognizer?.delegate = self     }     override func pushViewController(_ viewController: UIViewController, animated: Bool) {         applyTint(viewController.navigationItem)         super.pushViewController(viewController, animated: animated)     }      override func popViewController(animated: Bool) -> UIViewController? {         let viewController = super.popViewController(animated: animated)          applyTint(self.topViewController?.navigationItem)         return viewController     }      override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? {         let result = super.popToViewController(viewController, animated: animated)          applyTint(viewController.navigationItem)         return result     }      override func popToRootViewController(animated: Bool) -> [UIViewController]? {         let result = super.popToRootViewController(animated: animated)          applyTint(self.topViewController?.navigationItem)         return result     }      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {         return true     }      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {         return (otherGestureRecognizer is UIScreenEdgePanGestureRecognizer)     } } 

Note: The coordination of the color animation is done by the navigation controller

Answers 2

I updated my previous answer. I did animation effect without using transition coordinator and they are smooth in every case like push/pop/swipe-back.

https://stackoverflow.com/a/40272975/5433235

Also, you can check it on my github project

Hope it helps you :)

Answers 3

See my answer on the same question: How to set navigation bar to transparent in iOS 11

You can set navbar to transparent and animate view below it.

Read More

Sunday, August 13, 2017

how to use Objective-C project in my Swift project

Leave a Comment

Note: I know How to call Objective-C code from Swift, but I don't know below,

I'm new to ios. I want to use this EsptouchForIOS's Demo in my project. The demo is write in OC, it has a storyboard and controller. I want to know how to integrate the demo in my swift project, and use that storyboard and it's controller in my swift project.

3 Answers

Answers 1

I'll start writing from the very beginning. Suppose you have a project in Objective-C and now you want to continue your project's development in Swift. Follow the below guidelines: (This intends to your specific needs)

First choose to add a new file from File->New->File. In this process select your language as Swift. In the final step here, you will be prompted to Create Bridging Header. Select that:

Bridging Header creation

Now build your project once (+B). You may get an error like this:

iOS SDK version error message

Change your target's minimum deployment to the version that Swift supports. (Example in the below screenshot) Changing deployment target

To use Objective-C resources in Swift files:

Now that you've got one ProjectName-Bridging-Header.h file in your project. If you want to use any Objective-C class in your Swift files, you just include the header file of that class in this bridging header file. Like in this project, you have ESP_NetUtil and ESPViewController class and their header files too. You want to expose them to Swift and use them later in Swift code. So import them in this bridging header file:

Importing Objective-C header files in Bridging Header

Build once again. Now you can go to your Swift file. And use the Objective-C classes as like you use any resource in swift. See:

Objective-C classes used in Swift

N.B: You must expose all the class headers (that you're intending to use later in Swift) in that bridging header file

To use Swift resources in Objective-C files:

Now you may wonder, I've successfully used Objective-C resources in Swift. What about the opposite? Yes! You can do the opposite too. Find your Target->Build Settings->Swift Compiler - General->Objective-C Generated Interface Header Name. This is the header file you will be using inside your Objective-C classes for any Swift to Objective-C interoperability. To know more check here.

Objective-C Generated Interface Header Name

Now inside any of your Objective-C class, import that interface header and use Swift resources in Objective-C code:

Import and using Swift resource in Objective-C

You will get more understanding from the official apple documentation.

You can checkout the worked out version of your linked project here with Objective-C-Swift interoperability.

Answers 2

So according to your question, you have added an objective C bridge in your swift project using How to call Objective-C code from Swift.

Now, import all headers (.h) files of your objective-c source code (demo project) that you want to direct use in swift file.

For example, your demo project has EsptouchForIOS following header (file with extension .h) files in project source code.

ESPAppDelegate.h, ESPDataCode.h, ESPTouchDelegate.h

import a header file in your bridge, which you want to use in your swift code. Suppose in your swift code you want touch delegate ESPTouchDelegate then write,

#import "ESPTouchDelegate.h" 

Here is snapshot of your demo integration in my Test Swift project with bridge

enter image description here

and import statements.

enter image description here

Now, there is function/method in an objective C file getValue

enter image description here

which is used/accessed in swift project/file.

enter image description here

Similarly, you can import as many files (source headers) as you want in bridge and use the same files (source code) in swift.

Answers 3

I have never tried to use objective-c from swift project. But I normally used swift classes from my objective-c project. I usually follow this instructions https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html from apple developer website.

Read More

Wednesday, February 15, 2017

iOS: Default IB popover transition does strange things

Leave a Comment

I bumped into a strange issue where when a view controller is presented in a popover, the presenting view controllers view is not snapped to the bottom edge of it's superview, in that case the window itself.

AppDelegate code:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {     // Override point for customization after application launch.     let window = UIWindow(frame: UIScreen.main.bounds)     window.rootViewController = UIStoryboard(name: "NavigationController", bundle: Bundle.main).instantiateInitialViewController()     window.makeKeyAndVisible()     window.backgroundColor = .yellow // Needed to detect the issue.     self.window = window      return true } 

Storyboard to test:

<?xml version="1.0" encoding="UTF-8"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="4lt-Df-IeL">     <device id="retina5_5" orientation="portrait">         <adaptation id="fullscreen"/>     </device>     <dependencies>         <deployment identifier="iOS"/>         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>     </dependencies>     <scenes>         <!--Navigation Controller (locked)-->         <scene sceneID="ryr-GW-wBz">             <objects>                 <viewController storyboardIdentifier="NavigationController" definesPresentationContext="YES" id="4lt-Df-IeL" userLabel="Navigation Controller (locked)" sceneMemberID="viewController">                     <layoutGuides>                         <viewControllerLayoutGuide type="top" id="gxR-oc-l6J"/>                         <viewControllerLayoutGuide type="bottom" id="Py7-tH-kHP"/>                     </layoutGuides>                     <view key="view" contentMode="scaleToFill" id="865-DH-bRZ" userLabel="Background View">                         <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>                         <subviews>                             <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="535-Cq-y2B">                                 <rect key="frame" x="54" y="64" width="360" height="618"/>                                 <connections>                                     <segue destination="G7K-pY-ZSi" kind="embed" identifier="ContainerViewController" id="0Mw-mh-5NT"/>                                 </connections>                             </containerView>                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xLz-jb-9OM" userLabel="Left View">                                 <rect key="frame" x="0.0" y="64" width="54" height="672"/>                                 <subviews>                                     <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="bls-BC-WIu" userLabel="Left Stack View">                                         <rect key="frame" x="0.0" y="0.0" width="54" height="672"/>                                     </stackView>                                     <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Zbx-7u-5oe" userLabel="Hairline View">                                         <rect key="frame" x="53" y="0.0" width="1" height="672"/>                                         <color key="backgroundColor" red="0.43079092749999998" green="0.1140047376" blue="0.1180379456" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>                                         <constraints>                                             <constraint firstAttribute="width" constant="1" id="kna-vk-GlF" customClass="OnePixelConstraint" customModule="segue_issue" customModuleProvider="target"/>                                         </constraints>                                     </view>                                 </subviews>                                 <color key="backgroundColor" red="0.627" green="0.114" blue="0.14099999999999999" alpha="1" colorSpace="calibratedRGB"/>                                 <constraints>                                     <constraint firstAttribute="width" constant="54" id="2bA-Ap-aup"/>                                     <constraint firstItem="bls-BC-WIu" firstAttribute="leading" secondItem="xLz-jb-9OM" secondAttribute="leading" id="3sM-Bd-eEq"/>                                     <constraint firstAttribute="bottom" secondItem="Zbx-7u-5oe" secondAttribute="bottom" id="9PW-0y-kBv"/>                                     <constraint firstAttribute="trailing" secondItem="Zbx-7u-5oe" secondAttribute="trailing" id="RlN-G4-8iD"/>                                     <constraint firstAttribute="trailing" secondItem="bls-BC-WIu" secondAttribute="trailing" id="anE-HT-oQp"/>                                     <constraint firstItem="Zbx-7u-5oe" firstAttribute="top" secondItem="xLz-jb-9OM" secondAttribute="top" id="bxG-xS-wdV"/>                                     <constraint firstItem="bls-BC-WIu" firstAttribute="top" secondItem="xLz-jb-9OM" secondAttribute="top" id="ds3-z5-Dpv"/>                                     <constraint firstAttribute="bottom" secondItem="bls-BC-WIu" secondAttribute="bottom" id="f9r-FV-enN"/>                                 </constraints>                             </view>                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="V3o-XH-Xkn" userLabel="Bottom View">                                 <rect key="frame" x="0.0" y="682" width="414" height="54"/>                                 <subviews>                                     <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="fM3-hA-kGa" userLabel="Bottom Stack View">                                         <rect key="frame" x="0.0" y="0.0" width="414" height="54"/>                                     </stackView>                                     <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gtr-bO-frP" userLabel="Hairline View">                                         <rect key="frame" x="0.0" y="0.0" width="414" height="1"/>                                         <color key="backgroundColor" red="0.43079092749999998" green="0.1140047376" blue="0.1180379456" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>                                         <constraints>                                             <constraint firstAttribute="height" constant="1" id="K5f-m2-BpE" customClass="OnePixelConstraint" customModule="segue_issue" customModuleProvider="target"/>                                         </constraints>                                     </view>                                 </subviews>                                 <color key="backgroundColor" red="0.627" green="0.114" blue="0.14099999999999999" alpha="1" colorSpace="calibratedRGB"/>                                 <constraints>                                     <constraint firstItem="Gtr-bO-frP" firstAttribute="leading" secondItem="V3o-XH-Xkn" secondAttribute="leading" id="9fX-NI-RdG"/>                                     <constraint firstAttribute="bottom" secondItem="fM3-hA-kGa" secondAttribute="bottom" id="KxS-jW-8Rh"/>                                     <constraint firstItem="fM3-hA-kGa" firstAttribute="top" secondItem="V3o-XH-Xkn" secondAttribute="top" id="VLk-U3-Dgt"/>                                     <constraint firstAttribute="trailing" secondItem="Gtr-bO-frP" secondAttribute="trailing" id="aDz-aj-8ah"/>                                     <constraint firstAttribute="trailing" secondItem="fM3-hA-kGa" secondAttribute="trailing" id="bzw-Bv-nvH"/>                                     <constraint firstAttribute="height" constant="54" id="ghd-qS-t3l"/>                                     <constraint firstItem="Gtr-bO-frP" firstAttribute="top" secondItem="V3o-XH-Xkn" secondAttribute="top" id="nZz-Tn-i7X"/>                                     <constraint firstItem="fM3-hA-kGa" firstAttribute="leading" secondItem="V3o-XH-Xkn" secondAttribute="leading" id="sxO-BS-rki"/>                                 </constraints>                             </view>                             <navigationBar contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SAV-Rj-fPh">                                 <rect key="frame" x="0.0" y="0.0" width="414" height="64"/>                                 <color key="barTintColor" red="0.627" green="0.114" blue="0.14099999999999999" alpha="1" colorSpace="calibratedRGB"/>                                 <textAttributes key="titleTextAttributes">                                     <fontDescription key="fontDescription" type="system" pointSize="18"/>                                     <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>                                     <offsetWrapper key="textShadowOffset" horizontal="0.0" vertical="0.0"/>                                 </textAttributes>                             </navigationBar>                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NmK-Xz-xl3" userLabel="Navigation Bar Hairline">                                 <rect key="frame" x="0.0" y="64" width="414" height="1"/>                                 <color key="backgroundColor" red="0.43079092750694548" green="0.11400473755687479" blue="0.11803794560598729" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>                                 <constraints>                                     <constraint firstAttribute="height" constant="1" id="ENM-UW-O3D" customClass="OnePixelConstraint" customModule="segue_issue" customModuleProvider="target"/>                                 </constraints>                             </view>                             <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="alk-Uo-pMv">                                 <rect key="frame" x="192" y="0.0" width="46" height="30"/>                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>                                 <state key="normal" title="Button"/>                                 <connections>                                     <segue destination="G7K-pY-ZSi" kind="popoverPresentation" popoverAnchorView="alk-Uo-pMv" id="XuB-dB-CVh">                                         <popoverArrowDirection key="popoverArrowDirection" up="YES" down="YES" left="YES" right="YES"/>                                     </segue>                                 </connections>                             </button>                         </subviews>                         <constraints>                             <constraint firstItem="535-Cq-y2B" firstAttribute="leading" secondItem="xLz-jb-9OM" secondAttribute="trailing" id="0Ya-fi-fwL"/>                             <constraint firstItem="535-Cq-y2B" firstAttribute="trailing" secondItem="865-DH-bRZ" secondAttribute="trailing" id="2xc-7B-pd6"/>                             <constraint firstAttribute="trailing" secondItem="V3o-XH-Xkn" secondAttribute="trailing" id="4Qi-nQ-1b5"/>                             <constraint firstAttribute="trailing" secondItem="NmK-Xz-xl3" secondAttribute="trailing" id="4nX-tS-V49"/>                             <constraint firstAttribute="bottom" secondItem="V3o-XH-Xkn" secondAttribute="top" constant="54" id="8oE-54-0JN"/>                             <constraint firstItem="SAV-Rj-fPh" firstAttribute="top" secondItem="865-DH-bRZ" secondAttribute="top" id="DdA-dN-ucd"/>                             <constraint firstItem="V3o-XH-Xkn" firstAttribute="leading" secondItem="865-DH-bRZ" secondAttribute="leading" id="KzS-R2-JSi"/>                             <constraint firstItem="SAV-Rj-fPh" firstAttribute="bottom" secondItem="gxR-oc-l6J" secondAttribute="bottom" constant="44" id="Okf-F1-Bwv">                                 <variation key="heightClass=regular-widthClass=regular" constant="64"/>                             </constraint>                             <constraint firstItem="NmK-Xz-xl3" firstAttribute="top" secondItem="SAV-Rj-fPh" secondAttribute="bottom" id="Z7z-vD-Ypi"/>                             <constraint firstItem="SAV-Rj-fPh" firstAttribute="leading" secondItem="865-DH-bRZ" secondAttribute="leading" id="ZYY-Wz-1eh"/>                             <constraint firstAttribute="trailing" secondItem="SAV-Rj-fPh" secondAttribute="trailing" id="ein-Qv-Vwc"/>                             <constraint firstAttribute="bottom" secondItem="xLz-jb-9OM" secondAttribute="bottom" id="fmP-cS-nYy"/>                             <constraint firstItem="NmK-Xz-xl3" firstAttribute="leading" secondItem="865-DH-bRZ" secondAttribute="leading" id="nYe-rf-Zht"/>                             <constraint firstItem="xLz-jb-9OM" firstAttribute="top" secondItem="SAV-Rj-fPh" secondAttribute="bottom" id="tqI-Vx-dCH"/>                             <constraint firstAttribute="leading" secondItem="xLz-jb-9OM" secondAttribute="trailing" constant="-54" id="vuB-AY-U4j"/>                             <constraint firstItem="V3o-XH-Xkn" firstAttribute="top" secondItem="535-Cq-y2B" secondAttribute="bottom" id="wCH-Rs-saK"/>                             <constraint firstItem="535-Cq-y2B" firstAttribute="top" secondItem="SAV-Rj-fPh" secondAttribute="bottom" id="yeH-A6-JEo"/>                         </constraints>                     </view>                     <extendedEdge key="edgesForExtendedLayout"/>                     <simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="lightContent"/>                     <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>                     <size key="freeformSize" width="414" height="736"/>                 </viewController>                 <placeholder placeholderIdentifier="IBFirstResponder" id="bpj-us-mEU" userLabel="First Responder" sceneMemberID="firstResponder"/>             </objects>             <point key="canvasLocation" x="681" y="-398"/>         </scene>         <!--View Controller-->         <scene sceneID="O4R-kZ-Pdk">             <objects>                 <viewController storyboardIdentifier="ContainerViewController" id="G7K-pY-ZSi" sceneMemberID="viewController">                     <layoutGuides>                         <viewControllerLayoutGuide type="top" id="ffW-Hu-TSB"/>                         <viewControllerLayoutGuide type="bottom" id="0mf-BW-Ssn"/>                     </layoutGuides>                     <view key="view" contentMode="scaleToFill" id="x2i-Md-44j">                         <rect key="frame" x="0.0" y="0.0" width="360" height="618"/>                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>                         <color key="backgroundColor" red="0.94235802664974622" green="0.91986702625601058" blue="0.90890064764795653" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>                     </view>                 </viewController>                 <placeholder placeholderIdentifier="IBFirstResponder" id="B2f-Up-4gW" userLabel="First Responder" sceneMemberID="firstResponder"/>             </objects>             <point key="canvasLocation" x="1537.68115942029" y="-398.64130434782612"/>         </scene>     </scenes>     <inferredMetricsTieBreakers>         <segue reference="XuB-dB-CVh"/>     </inferredMetricsTieBreakers>     <color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/> </document> 

Copy everything inside a new project, remove main as the main storyboard to launch.

  1. Run the project on an iPhone X Plus simulator (or real device).
  2. Click on the bottom to present a view controller. 2.1. Enable slow motion (simulator only).
  3. Rotate your device.

The issue I'm talking about is when the yellow view is visible (window). One rotation does layout the presenting view controller correctly, the other does begin in wrong state.

enter image description here

Is that a bug that my storyboard has or something from UIKit?

Update: The project uses latest iOS SDK and no deprecated APIs (as shown above 'almost everything is build with IB').

Update 2: I updated the original sentence to make it crystal clear that it's not a UIPopoverController.

0 Answers

Read More

Sunday, February 5, 2017

iOS: Default IB popover transition does strange things

Leave a Comment

I bumped into a strange issue where when a popover view controller is presended, the presenting view controllers view is not snapped to the bottom edge of it's superview, in that case the window itself.

AppDelegate code:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {     // Override point for customization after application launch.     let window = UIWindow(frame: UIScreen.main.bounds)     window.rootViewController = UIStoryboard(name: "NavigationController", bundle: Bundle.main).instantiateInitialViewController()     window.makeKeyAndVisible()     window.backgroundColor = .yellow // Needed to detect the issue.     self.window = window      return true } 

Storyboard to test:

<?xml version="1.0" encoding="UTF-8"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="4lt-Df-IeL">     <device id="retina5_5" orientation="portrait">         <adaptation id="fullscreen"/>     </device>     <dependencies>         <deployment identifier="iOS"/>         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>     </dependencies>     <scenes>         <!--Navigation Controller (locked)-->         <scene sceneID="ryr-GW-wBz">             <objects>                 <viewController storyboardIdentifier="NavigationController" definesPresentationContext="YES" id="4lt-Df-IeL" userLabel="Navigation Controller (locked)" sceneMemberID="viewController">                     <layoutGuides>                         <viewControllerLayoutGuide type="top" id="gxR-oc-l6J"/>                         <viewControllerLayoutGuide type="bottom" id="Py7-tH-kHP"/>                     </layoutGuides>                     <view key="view" contentMode="scaleToFill" id="865-DH-bRZ" userLabel="Background View">                         <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>                         <subviews>                             <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="535-Cq-y2B">                                 <rect key="frame" x="54" y="64" width="360" height="618"/>                                 <connections>                                     <segue destination="G7K-pY-ZSi" kind="embed" identifier="ContainerViewController" id="0Mw-mh-5NT"/>                                 </connections>                             </containerView>                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xLz-jb-9OM" userLabel="Left View">                                 <rect key="frame" x="0.0" y="64" width="54" height="672"/>                                 <subviews>                                     <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="bls-BC-WIu" userLabel="Left Stack View">                                         <rect key="frame" x="0.0" y="0.0" width="54" height="672"/>                                     </stackView>                                     <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Zbx-7u-5oe" userLabel="Hairline View">                                         <rect key="frame" x="53" y="0.0" width="1" height="672"/>                                         <color key="backgroundColor" red="0.43079092749999998" green="0.1140047376" blue="0.1180379456" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>                                         <constraints>                                             <constraint firstAttribute="width" constant="1" id="kna-vk-GlF" customClass="OnePixelConstraint" customModule="segue_issue" customModuleProvider="target"/>                                         </constraints>                                     </view>                                 </subviews>                                 <color key="backgroundColor" red="0.627" green="0.114" blue="0.14099999999999999" alpha="1" colorSpace="calibratedRGB"/>                                 <constraints>                                     <constraint firstAttribute="width" constant="54" id="2bA-Ap-aup"/>                                     <constraint firstItem="bls-BC-WIu" firstAttribute="leading" secondItem="xLz-jb-9OM" secondAttribute="leading" id="3sM-Bd-eEq"/>                                     <constraint firstAttribute="bottom" secondItem="Zbx-7u-5oe" secondAttribute="bottom" id="9PW-0y-kBv"/>                                     <constraint firstAttribute="trailing" secondItem="Zbx-7u-5oe" secondAttribute="trailing" id="RlN-G4-8iD"/>                                     <constraint firstAttribute="trailing" secondItem="bls-BC-WIu" secondAttribute="trailing" id="anE-HT-oQp"/>                                     <constraint firstItem="Zbx-7u-5oe" firstAttribute="top" secondItem="xLz-jb-9OM" secondAttribute="top" id="bxG-xS-wdV"/>                                     <constraint firstItem="bls-BC-WIu" firstAttribute="top" secondItem="xLz-jb-9OM" secondAttribute="top" id="ds3-z5-Dpv"/>                                     <constraint firstAttribute="bottom" secondItem="bls-BC-WIu" secondAttribute="bottom" id="f9r-FV-enN"/>                                 </constraints>                             </view>                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="V3o-XH-Xkn" userLabel="Bottom View">                                 <rect key="frame" x="0.0" y="682" width="414" height="54"/>                                 <subviews>                                     <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="fM3-hA-kGa" userLabel="Bottom Stack View">                                         <rect key="frame" x="0.0" y="0.0" width="414" height="54"/>                                     </stackView>                                     <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gtr-bO-frP" userLabel="Hairline View">                                         <rect key="frame" x="0.0" y="0.0" width="414" height="1"/>                                         <color key="backgroundColor" red="0.43079092749999998" green="0.1140047376" blue="0.1180379456" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>                                         <constraints>                                             <constraint firstAttribute="height" constant="1" id="K5f-m2-BpE" customClass="OnePixelConstraint" customModule="segue_issue" customModuleProvider="target"/>                                         </constraints>                                     </view>                                 </subviews>                                 <color key="backgroundColor" red="0.627" green="0.114" blue="0.14099999999999999" alpha="1" colorSpace="calibratedRGB"/>                                 <constraints>                                     <constraint firstItem="Gtr-bO-frP" firstAttribute="leading" secondItem="V3o-XH-Xkn" secondAttribute="leading" id="9fX-NI-RdG"/>                                     <constraint firstAttribute="bottom" secondItem="fM3-hA-kGa" secondAttribute="bottom" id="KxS-jW-8Rh"/>                                     <constraint firstItem="fM3-hA-kGa" firstAttribute="top" secondItem="V3o-XH-Xkn" secondAttribute="top" id="VLk-U3-Dgt"/>                                     <constraint firstAttribute="trailing" secondItem="Gtr-bO-frP" secondAttribute="trailing" id="aDz-aj-8ah"/>                                     <constraint firstAttribute="trailing" secondItem="fM3-hA-kGa" secondAttribute="trailing" id="bzw-Bv-nvH"/>                                     <constraint firstAttribute="height" constant="54" id="ghd-qS-t3l"/>                                     <constraint firstItem="Gtr-bO-frP" firstAttribute="top" secondItem="V3o-XH-Xkn" secondAttribute="top" id="nZz-Tn-i7X"/>                                     <constraint firstItem="fM3-hA-kGa" firstAttribute="leading" secondItem="V3o-XH-Xkn" secondAttribute="leading" id="sxO-BS-rki"/>                                 </constraints>                             </view>                             <navigationBar contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SAV-Rj-fPh">                                 <rect key="frame" x="0.0" y="0.0" width="414" height="64"/>                                 <color key="barTintColor" red="0.627" green="0.114" blue="0.14099999999999999" alpha="1" colorSpace="calibratedRGB"/>                                 <textAttributes key="titleTextAttributes">                                     <fontDescription key="fontDescription" type="system" pointSize="18"/>                                     <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>                                     <offsetWrapper key="textShadowOffset" horizontal="0.0" vertical="0.0"/>                                 </textAttributes>                             </navigationBar>                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NmK-Xz-xl3" userLabel="Navigation Bar Hairline">                                 <rect key="frame" x="0.0" y="64" width="414" height="1"/>                                 <color key="backgroundColor" red="0.43079092750694548" green="0.11400473755687479" blue="0.11803794560598729" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>                                 <constraints>                                     <constraint firstAttribute="height" constant="1" id="ENM-UW-O3D" customClass="OnePixelConstraint" customModule="segue_issue" customModuleProvider="target"/>                                 </constraints>                             </view>                             <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="alk-Uo-pMv">                                 <rect key="frame" x="192" y="0.0" width="46" height="30"/>                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>                                 <state key="normal" title="Button"/>                                 <connections>                                     <segue destination="G7K-pY-ZSi" kind="popoverPresentation" popoverAnchorView="alk-Uo-pMv" id="XuB-dB-CVh">                                         <popoverArrowDirection key="popoverArrowDirection" up="YES" down="YES" left="YES" right="YES"/>                                     </segue>                                 </connections>                             </button>                         </subviews>                         <constraints>                             <constraint firstItem="535-Cq-y2B" firstAttribute="leading" secondItem="xLz-jb-9OM" secondAttribute="trailing" id="0Ya-fi-fwL"/>                             <constraint firstItem="535-Cq-y2B" firstAttribute="trailing" secondItem="865-DH-bRZ" secondAttribute="trailing" id="2xc-7B-pd6"/>                             <constraint firstAttribute="trailing" secondItem="V3o-XH-Xkn" secondAttribute="trailing" id="4Qi-nQ-1b5"/>                             <constraint firstAttribute="trailing" secondItem="NmK-Xz-xl3" secondAttribute="trailing" id="4nX-tS-V49"/>                             <constraint firstAttribute="bottom" secondItem="V3o-XH-Xkn" secondAttribute="top" constant="54" id="8oE-54-0JN"/>                             <constraint firstItem="SAV-Rj-fPh" firstAttribute="top" secondItem="865-DH-bRZ" secondAttribute="top" id="DdA-dN-ucd"/>                             <constraint firstItem="V3o-XH-Xkn" firstAttribute="leading" secondItem="865-DH-bRZ" secondAttribute="leading" id="KzS-R2-JSi"/>                             <constraint firstItem="SAV-Rj-fPh" firstAttribute="bottom" secondItem="gxR-oc-l6J" secondAttribute="bottom" constant="44" id="Okf-F1-Bwv">                                 <variation key="heightClass=regular-widthClass=regular" constant="64"/>                             </constraint>                             <constraint firstItem="NmK-Xz-xl3" firstAttribute="top" secondItem="SAV-Rj-fPh" secondAttribute="bottom" id="Z7z-vD-Ypi"/>                             <constraint firstItem="SAV-Rj-fPh" firstAttribute="leading" secondItem="865-DH-bRZ" secondAttribute="leading" id="ZYY-Wz-1eh"/>                             <constraint firstAttribute="trailing" secondItem="SAV-Rj-fPh" secondAttribute="trailing" id="ein-Qv-Vwc"/>                             <constraint firstAttribute="bottom" secondItem="xLz-jb-9OM" secondAttribute="bottom" id="fmP-cS-nYy"/>                             <constraint firstItem="NmK-Xz-xl3" firstAttribute="leading" secondItem="865-DH-bRZ" secondAttribute="leading" id="nYe-rf-Zht"/>                             <constraint firstItem="xLz-jb-9OM" firstAttribute="top" secondItem="SAV-Rj-fPh" secondAttribute="bottom" id="tqI-Vx-dCH"/>                             <constraint firstAttribute="leading" secondItem="xLz-jb-9OM" secondAttribute="trailing" constant="-54" id="vuB-AY-U4j"/>                             <constraint firstItem="V3o-XH-Xkn" firstAttribute="top" secondItem="535-Cq-y2B" secondAttribute="bottom" id="wCH-Rs-saK"/>                             <constraint firstItem="535-Cq-y2B" firstAttribute="top" secondItem="SAV-Rj-fPh" secondAttribute="bottom" id="yeH-A6-JEo"/>                         </constraints>                     </view>                     <extendedEdge key="edgesForExtendedLayout"/>                     <simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="lightContent"/>                     <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>                     <size key="freeformSize" width="414" height="736"/>                 </viewController>                 <placeholder placeholderIdentifier="IBFirstResponder" id="bpj-us-mEU" userLabel="First Responder" sceneMemberID="firstResponder"/>             </objects>             <point key="canvasLocation" x="681" y="-398"/>         </scene>         <!--View Controller-->         <scene sceneID="O4R-kZ-Pdk">             <objects>                 <viewController storyboardIdentifier="ContainerViewController" id="G7K-pY-ZSi" sceneMemberID="viewController">                     <layoutGuides>                         <viewControllerLayoutGuide type="top" id="ffW-Hu-TSB"/>                         <viewControllerLayoutGuide type="bottom" id="0mf-BW-Ssn"/>                     </layoutGuides>                     <view key="view" contentMode="scaleToFill" id="x2i-Md-44j">                         <rect key="frame" x="0.0" y="0.0" width="360" height="618"/>                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>                         <color key="backgroundColor" red="0.94235802664974622" green="0.91986702625601058" blue="0.90890064764795653" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>                     </view>                 </viewController>                 <placeholder placeholderIdentifier="IBFirstResponder" id="B2f-Up-4gW" userLabel="First Responder" sceneMemberID="firstResponder"/>             </objects>             <point key="canvasLocation" x="1537.68115942029" y="-398.64130434782612"/>         </scene>     </scenes>     <inferredMetricsTieBreakers>         <segue reference="XuB-dB-CVh"/>     </inferredMetricsTieBreakers>     <color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/> </document> 

Copy everything inside a new project, remove main as the main storyboard to launch.

  1. Run the project on an iPhone X Plus simulator (or real device).
  2. Click on the bottom to present a view controller. 2.1. Enable slow motion (simulator only).
  3. Rotate your device.

The issue I'm talking about is when the yellow view is visible (window). One rotation does layout the presenting view controller correctly, the other does begin in wrong state.

enter image description here

Is that a bug that my storyboard has or something from UIKit?

0 Answers

Read More

Saturday, April 16, 2016

Understanding the value of UIViewController transitions

Leave a Comment

I am trying to learn some new iOS programming patterns. I have read a bunch about the UIViewController transitioning APIs added in iOS 7. They look cool but also feel pretty heavy for what seems like a simpler task.

Consider this use case: I have a custom container view controller that manages "slides". It holds an array of slide view controllers and the user can move forward and backward though them by tapping a button.

I can implement the transition for this as follows:

private func transitionToViewController(viewController: UIViewController, direction: TransitionDirection = .Forward, animated: Bool = true) {     currentViewController.willMove(toParentViewController: nil)     addChildViewController(viewController)     // ... set up frames, other animation prep ...     contentContainerView.addSubview(comingView)     UIView.animate(duration: 0.5, animations: {          // do the animations     }) { (finished) in         leavingView.removeFromSuperview()         self.currentViewController.removeFromParentViewController()         viewController.didMove(toParentViewController: self)         // final clean up     }  } 

How would the newer transitioning APIs improve this? From what I understand, these APIs are even more complicated to use if you are rolling your own container view controllers (see custom-container view controller transitions.

Is the value in the transitioning APIs mostly for interactive transitions?

Thanks for clarifying

2 Answers

Answers 1

I think the new transitioning API (UIViewControllerTransitioningDelegate and friends) is simply a final step in the generalization of view transitions between controllers.

In first versions of UIKit we had to hack the system transition code to get any custom transitions at all. Years later we got controller containment that made it possible to manage view controllers as first-class citizens and create our own interactive transitions. The final step is having a full-featured general system API for any transition that you can dream of – that’s the new transition API.

The new API makes it possible to extract the transitions into standalone classes. Which, in turn, makes it finally possible to just download a transition library off GitHub and plug it into your existing code as a simple transition delegate. No need to derive your view controllers from some particular superclass, no need to use a third-party controller container, no need to add extensions to UIKit classes. Now the transitions are finally first-class citizens in UIKit, too.

Answers 2

How would the newer transitioning APIs improve this?

TL;DR Maintainability via Encapsulation, Reusability, Testability.

Encapsulation: The comments indicate that there's logic to set up and state to track with the animation. Your view controller is probably plenty big enough already; putting transition logic somewhere else makes each piece smaller and therefore more maintainable.

Reusability: What's the next thing you'll do after this? Set up the transition back from the transitioned to controller, no doubt. And is it likely to be a reversal of this animation? Pretty likely. So you'll copy and paste this code to that controller and reverse it there, probably. Now you've got two copies. One copy using the transitions would be more maintainable. (Also note the existence of pods of custom transitions, as reusability and shareability go hand in hand.)

Testability: Code embedded in a heavyweight view controller is notoriously difficult to test. A custom transition can be tested in isolation without the state overhead of the live views.

So for any code you intend to look at more than once, the transitioning APIs are probably worth the effort!

Read More

Thursday, April 14, 2016

Preserve custom tabbar view state between view controllers

Leave a Comment

We have a custom view, which looks like a tabbar but is ultimately a subclass of UIView.

The view is shown as a tabbar would at the bottom of a UIViewController. When an image is touched in the view controller we transition to another view controller.

The second view controller has the same fake tabbar view being shown at the bottom. The user can close the second view controller and it will transition back to the first.

What is the best way to keep the same view and its state for both view controllers? For example part of the fake tabbar might be a usable button with a badge icon showing (2). If that is touched it would go down to (1). This would need to be reflected on both instances of the view.

Would the correct approach be to just use prepareForSegue as normal and keep updating the view state or passing the views instance around? Or is there a better approach?

4 Answers

Answers 1

I think the best approach is to implement something similar with the native tab bar. You can achieve this by implementing a container view . How you do that is a long story to post here but there are many resources on the internet. Basically you will have the same fake bar and your view controller will be shown in container view that should be put just above the tab bar. The view controller with both the container view and the tab bar should manage the transitions and update the bar.

Answers 2

Yeah, just as Jelly said I'd go the parent/child view controller route, with the 'tab bar' managing adding/removing the view controllers and associated views in response to touch events.

Answers 3

I am working on the same scenerio. In a UIViewController take your tabBar view at the bottom and above that take a blank UIView. Now on click of tabBar button, add and remove your new ViewController's view using AutoLayout like as -

#pragma mark - TAB BAR METHODS  -(void)setSelecedView:(VIEWSELECTION)selecedView {     [self RemoveChildViewControllers ];     switch (selecedView)     {         case VIEWSELECTION_HOME:         {             HomeViewController *homeVC = [[HomeViewController alloc]initWithNibName:@"HomeViewController" bundle:nil];             self.titleString=@"Wellborn Company App";             [self displayContentController:homeVC OnView:self.DumpingView];         }             break;         case VIEWSELECTION_SEARCH:         {             SearchViewController *searchVC = [[SearchViewController alloc]initWithNibName:@"SearchViewController" bundle:nil];             self.titleString=@"Search";             [self displayContentController:searchVC  OnView:self.DumpingView];         }             break; }}  #pragma mark - VC Adding/Removing Methods  - (void)RemoveChildViewControllers {     NSArray *childVCArray = [self childViewControllers];      for ( __strong UIViewController *childvc in childVCArray)     {         [childvc willMoveToParentViewController:nil];         [childvc.view removeFromSuperview];         [childvc removeFromParentViewController];     } }  - (void)displayContentController:(UIViewController*) content OnView:(UIView*)parentView {     [self addChildViewController:content];     [parentView addSubview:content.view];      NSDictionary *views = @{                             @"childView" : content.view,                              };     NSArray *arr;      [content.view setTranslatesAutoresizingMaskIntoConstraints:NO];      arr = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[childView]-0-|" options:0 metrics:nil views:views];     [parentView addConstraints:arr];      arr = [NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[childView]-0-|" options:0 metrics:nil views:views];     [parentView addConstraints:arr];         [content didMoveToParentViewController:self]; }  

Answers 4

If it is just a view and your simply pushing view controllers on a navigation stack, then add your view to your navigation view controllers view.

[self.navigationController.view addSubview:view]; 
Read More

Monday, April 11, 2016

Will multiple presentViewController method calls throughout the view controllers lead to memory leak in iOS?

Leave a Comment

I know there has been a lot of discussions occurred related to this topic. But in all discussions all have discussed with 2 view controllers (A&B). My scenario is similar but different.

What will happen when there are multiple view controllers like A,B,C,D. So the presenting flow moves as ,

View controller A (Home) presents View controller B(List). Then from View controller B presents View Controller C (Details).Then from View Controller C presents View Controller D(Advanced Details). Then from View Controller D presents View Controller A , in order to navigate straight to Home !!!

What is the best practice for that ???

3 Answers

Answers 1

Not knowing your project structure and details of how you will display A,B,C,D,E,F and then from F back to A, I would take a wild guess and say that it may lead to a memory leak depending on what design patterns you employ to your UIViewControllers. As commented by @CaptJak in your questions, it has hard to tell if, how and when it will cause a memory leak, especially if you use delegation pattern to pass data around view controllers.

Personally, when I do complicated flows such as presenting multiple UIViewControllers and find myself needing to go back a few screens, I will neither pop the views on the stack up until the one I want is on top of the stack (if you are using navigation controller), dismiss view controller if it is presented modally, or unwind segues if I use them. The risk here might be the view controller's memory may have deallocated.

I would have commented but I don't have enough reputation. Take my answer with a grain of salt as I am a still quite fresh in iOS development.

EDIT: Thank you for the details provided in your app flow. Since you could use presentViewController, I am assuming you are running on a NavigationController? If that is the case, I would use popToViewController or popToRootViewController (if A is your root view controller) for this case instead of presenting A from D again. If A is presented from D again, I am guessing that you will have 2 instances of A in your VC stack which may lead to memory leak.

PopToViewController method

NSArray arrayOfVCs = self.navigationController.viewControllers; for(UIViewController *currentVC in arrayOfVCs) {    if([currentVC isKindOfClass:[ViewControllerA class])    {       [self.navigation.controller popToViewController:currentVC animated:YES]    } } 

PopToRootViewController method (assuming A is your root view in navigation controller)

[self.navigationController popToRootViewControllerAnimated:YES] 

Answers 2

I do believe it is possible to command + drag from a UIButton (or whatever else you are using to control your flow) to the view controller that you next want to present, so as to navigate properly. Then, you let go and select whatever method of presentation you want, and when you try it, it should work. I am assuming you use XCode for this, and I am not sure for other development applications.

This is designed to prevent a memory leak from occurring, but do note that you will have to create separate files for each separate view controller. Other manual forms of switching may result in memory leaks, depending on the contents of your situation.

Answers 3

Nothing bad if you will display newly created A view controller.

Read More