Search code examples
uiscrollviewuibuttonuistackviewstackview

Underline Single Button from Top menu bar


  1. As seen in the image MyTopBar I have 5 uibuttons inside a scroll view. I am using it as Navigation/Top bar
  2. I want to achieve an underline effect only on selected button among the 5 button. imageTarget
  3. I saw some libraries but it did not work as I expected them to
  4. I will be refreshing same view so that’s not really useful
  5. Can some tell me a simple way to achieve this underline affect?

Solution

  • There are various ways to do this. Here's one approach...

    • Put your buttons in a horizontal stack view
    • add a UIView as your "underline view"

    For your "underline view":

    • set height constraint to 4
    • set top constraint equal to the stack view's bottom
    • set widthAnchor equal to first button widthAnchor, constant 8 (4-pts wider)
    • set centerXAnchor equal to first button centerXAnchor

    Connect the width and center anchors to @IBOutlets in your custom class.

    When a button is tapped:

    • de-activate width and centerX constraints
    • set them to the tapped button
    • re-activate them
    • animate into position if desired

    Here is code for a sample implementation:

    protocol TopBarButtonTapProtocol {
        func didSelect(_ sender: UIButton)
    }
    
    class MyTopBar: UIView {
    
        var delegate: TopBarButtonTapProtocol?
    
        @IBOutlet var theStack: UIStackView!
        @IBOutlet var uView: UIView!
    
        @IBOutlet var uWidth: NSLayoutConstraint!
        @IBOutlet var uCenter: NSLayoutConstraint!
    
        override func didMoveToWindow() {
            super.didMoveToWindow()
    
            let btns = theStack.arrangedSubviews
    
            btns.forEach { v in
                guard let btn = v as? UIButton else {
                    fatalError("Stack view must contain buttons only!")
                }
                btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
            }
    
        }
    
        @objc func didTap(_ sender: UIButton) -> Void {
            uCenter.isActive = false
            uWidth.isActive = false
            uCenter = uView.centerXAnchor.constraint(equalTo: sender.centerXAnchor)
            uWidth = uView.widthAnchor.constraint(equalTo: sender.widthAnchor, constant: 8)
            uCenter.isActive = true
            uWidth.isActive = true
            UIView.animate(withDuration: 0.3) {
                self.layoutIfNeeded()
            }
            delegate?.didSelect(sender)
        }
    
    }
    
    class TestMyTopBarViewController: UIViewController, TopBarButtonTapProtocol {
    
        @IBOutlet var myTopBar: MyTopBar!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            myTopBar.delegate = self
        }
    
        func didSelect(_ sender: UIButton) {
            print("Top Bar button selected:", sender.currentTitle)
        }
    
    }
    

    and here is the source to an example Storyboard:

    <?xml version="1.0" encoding="UTF-8"?>
    <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="abg-zX-CIL">
        <device id="retina4_7" orientation="portrait" appearance="light"/>
        <dependencies>
            <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
            <capability name="Safe area layout guides" minToolsVersion="9.0"/>
            <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
        </dependencies>
        <scenes>
            <!--Test My Top Bar View Controller-->
            <scene sceneID="Jte-e1-dR6">
                <objects>
                    <viewController id="abg-zX-CIL" customClass="TestMyTopBarViewController" customModule="MiniScratch" customModuleProvider="target" sceneMemberID="viewController">
                        <view key="view" contentMode="scaleToFill" id="TsC-q0-s74">
                            <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                            <subviews>
                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kx6-5a-S6X" customClass="MyTopBar" customModule="MiniScratch" customModuleProvider="target">
                                    <rect key="frame" x="0.0" y="0.0" width="375" height="46"/>
                                    <subviews>
                                        <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="BXS-ke-LLN">
                                            <rect key="frame" x="0.0" y="0.0" width="375" height="46"/>
                                            <subviews>
                                                <stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="Gtj-bJ-9xt">
                                                    <rect key="frame" x="16" y="8" width="406" height="30"/>
                                                    <subviews>
                                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ljU-s4-4DJ">
                                                            <rect key="frame" x="0.0" y="0.0" width="65" height="30"/>
                                                            <state key="normal" title="FOR YOU">
                                                                <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                            </state>
                                                        </button>
                                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="242-wz-PkM">
                                                            <rect key="frame" x="81" y="0.0" width="75" height="30"/>
                                                            <state key="normal" title="TRENDING">
                                                                <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                            </state>
                                                        </button>
                                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c15-qj-lxv">
                                                            <rect key="frame" x="172" y="0.0" width="34" height="30"/>
                                                            <state key="normal" title="NEW">
                                                                <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                            </state>
                                                        </button>
                                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5DP-bs-qBc">
                                                            <rect key="frame" x="222" y="0.0" width="75" height="30"/>
                                                            <state key="normal" title="FEATURED">
                                                                <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                            </state>
                                                        </button>
                                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZfK-40-1VN">
                                                            <rect key="frame" x="313" y="0.0" width="93" height="30"/>
                                                            <state key="normal" title="BOOKMARKS">
                                                                <color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                            </state>
                                                        </button>
                                                    </subviews>
                                                </stackView>
                                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="974-mZ-VM1">
                                                    <rect key="frame" x="12" y="38" width="73" height="4"/>
                                                    <color key="backgroundColor" systemColor="systemRedColor" red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                    <constraints>
                                                        <constraint firstAttribute="height" constant="4" id="rvK-8e-XTq"/>
                                                    </constraints>
                                                </view>
                                            </subviews>
                                            <constraints>
                                                <constraint firstItem="Gtj-bJ-9xt" firstAttribute="trailing" secondItem="Zfb-Kx-C2I" secondAttribute="trailing" constant="16" id="4nw-75-kgv"/>
                                                <constraint firstItem="974-mZ-VM1" firstAttribute="centerX" secondItem="ljU-s4-4DJ" secondAttribute="centerX" id="9Ig-gt-a62"/>
                                                <constraint firstItem="Gtj-bJ-9xt" firstAttribute="leading" secondItem="Zfb-Kx-C2I" secondAttribute="leading" constant="16" id="EGu-UL-6tD"/>
                                                <constraint firstItem="Gtj-bJ-9xt" firstAttribute="bottom" secondItem="Zfb-Kx-C2I" secondAttribute="bottom" id="Fby-qZ-GyD"/>
                                                <constraint firstItem="974-mZ-VM1" firstAttribute="width" secondItem="ljU-s4-4DJ" secondAttribute="width" constant="8" id="GNh-W8-umn"/>
                                                <constraint firstItem="974-mZ-VM1" firstAttribute="top" secondItem="Gtj-bJ-9xt" secondAttribute="bottom" id="QeJ-UW-lPh"/>
                                                <constraint firstItem="Gtj-bJ-9xt" firstAttribute="top" secondItem="Zfb-Kx-C2I" secondAttribute="top" constant="8" id="gXv-a7-43N"/>
                                                <constraint firstItem="x1k-em-xS5" firstAttribute="bottom" secondItem="Gtj-bJ-9xt" secondAttribute="bottom" constant="8" id="yQ5-dH-18H"/>
                                            </constraints>
                                            <viewLayoutGuide key="contentLayoutGuide" id="Zfb-Kx-C2I"/>
                                            <viewLayoutGuide key="frameLayoutGuide" id="x1k-em-xS5"/>
                                        </scrollView>
                                    </subviews>
                                    <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                    <constraints>
                                        <constraint firstAttribute="trailing" secondItem="BXS-ke-LLN" secondAttribute="trailing" id="1Qh-mv-QIl"/>
                                        <constraint firstItem="BXS-ke-LLN" firstAttribute="top" secondItem="kx6-5a-S6X" secondAttribute="top" id="XBR-Uf-huR"/>
                                        <constraint firstItem="BXS-ke-LLN" firstAttribute="leading" secondItem="kx6-5a-S6X" secondAttribute="leading" id="YyI-HM-ve8"/>
                                        <constraint firstAttribute="bottom" secondItem="BXS-ke-LLN" secondAttribute="bottom" id="ioI-y3-Lmd"/>
                                    </constraints>
                                    <connections>
                                        <outlet property="theStack" destination="Gtj-bJ-9xt" id="5XU-eX-2NB"/>
                                        <outlet property="uCenter" destination="9Ig-gt-a62" id="TxH-Bf-Hy0"/>
                                        <outlet property="uView" destination="974-mZ-VM1" id="BZ0-ne-Fcb"/>
                                        <outlet property="uWidth" destination="GNh-W8-umn" id="aFq-4f-sQu"/>
                                    </connections>
                                </view>
                            </subviews>
                            <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                            <constraints>
                                <constraint firstItem="kx6-5a-S6X" firstAttribute="top" secondItem="u6w-w1-8iK" secondAttribute="top" id="KhJ-Id-pdJ"/>
                                <constraint firstItem="kx6-5a-S6X" firstAttribute="trailing" secondItem="u6w-w1-8iK" secondAttribute="trailing" id="R2z-YZ-Vkl"/>
                                <constraint firstItem="kx6-5a-S6X" firstAttribute="leading" secondItem="u6w-w1-8iK" secondAttribute="leading" id="ifV-RN-a6g"/>
                            </constraints>
                            <viewLayoutGuide key="safeArea" id="u6w-w1-8iK"/>
                        </view>
                        <navigationItem key="navigationItem" id="cpA-Qb-gmu">
                            <barButtonItem key="rightBarButtonItem" systemItem="add" id="mAr-W2-GXa">
                                <connections>
                                    <action selector="addBtnPressed:" destination="abg-zX-CIL" id="OIu-lZ-j2s"/>
                                </connections>
                            </barButtonItem>
                        </navigationItem>
                        <connections>
                            <outlet property="myTopBar" destination="kx6-5a-S6X" id="Ssi-YG-3F2"/>
                        </connections>
                    </viewController>
                    <placeholder placeholderIdentifier="IBFirstResponder" id="wfE-oG-abp" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
                </objects>
                <point key="canvasLocation" x="1703" y="1657"/>
            </scene>
        </scenes>
    </document>
    

    and here's how it looks at run-time, with the buttons scrolled to the left, and the "Featured" button selected:

    enter image description here