How to add UIImageViews to a UIStackView that is constrained to the centerXAnchor of a view? - uiimageview

I'm trying to add profile icons via UIImageViews to a UIStackView in order to keep the icons centered in a view. How would I go about adding UIImageViews of a fixed frame to a UIStackView and keep the UIStackView centered in the main view according to varying numbers of UIImageViews in the UIStackView?
let memberIcons: UIStackView = {
let iconView = UIStackView()
iconView.translatesAutoresizingMaskIntoConstraints = false
iconView.axis = .horizontal
iconView.spacing = 5
iconView.distribution = .equalSpacing
iconView.alignment = .center
return iconView
}()
for member in story!.members {
let circle = UIImageView()
circle.frame = CGRect(x: 0, y: 0, width: 36, height: 36)
circle.translatesAutoresizingMaskIntoConstraints = false
circle.layer.cornerRadius = CGFloat(circle.frame.width / 2)
circle.image = member.profilePicture
circle.contentMode = .scaleAspectFill
circle.clipsToBounds = true
memberIcons.addArrangedSubview(circle)
}

Because you set memberIcons.distribution = .equalSpace, the stack view will ask its subviews for their intrinsic sizes. When asked, the UIImage (i.e. circle) will calculate its intrinsic size as "image pixel size / scale", which is not what you want -- you want the image to be of fixed size (36 x 36).
Use Auto Layout on circle:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(memberIcons)
memberIcons.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
memberIcons.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// Limit the stack view's width to no more than 75% of the superview's width
// Adjust as needed
memberIcons.widthAnchor.constraint(lessThanOrEqualTo: view.widthAnchor, multiplier: 0.75).isActive = true
let width: CGFloat = 36.0
for member in story!.members {
// We don't care about the frame here as we're gonna use auto layout
let circle = UIImageView(frame: .zero)
circle.translatesAutoresizingMaskIntoConstraints = false
circle.layer.cornerRadius = width / 2
circle.image = member.profilePicture
circle.contentMode = .scaleAspectFill
circle.clipsToBounds = true
circle.layer.borderWidth = 1
circle.layer.borderColor = UIColor.lightGray.cgColor
memberIcons.addArrangedSubview(circle)
circle.widthAnchor.constraint(equalToConstant: width).isActive = true
circle.heightAnchor.constraint(equalToConstant: width).isActive = true
}
}
Result:
Because we limit the width of the UIStackView, there a maximum number of profile images you can add (7 in this case) before you get a bunch of auto layout error on the console. You can enclose the Stack View inside a Scroll View or use a Collection View for a matrix-like display.

Related

How can we make a spacer view with automatic width between two views inside UIStackview?

If we have 2 labels inside UIStackView, and I want to set a flexible width between them like this image:
what is the best way? in SwiftUI there is a function named: Spacer() , is there something similar in UIKit?
You can add a UIView to the stack to push each view in the UIStackView:
hStack:
hStack.axis = .horizontal
hStack.addArrangedSubview(label1)
hStack.addArrangedSubview(UIView.spacer(for: .horizontal))
hStack.addArrangedSubview(label2)
constraints:
hStack.translatesAutoresizingMaskIntoConstraints = false
hStack.topAnchor.constraint(equalTo: "depending on your view").isActive = true
hStack.bottomAnchor.constraint(equalTo: "depending on your view").isActive = true
hStack.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
and the extension to make this work:
// MARK: - Spacer
extension UIView {
static func spacer(size: CGFloat = .greatestFiniteMagnitude, for layout: NSLayoutConstraint.Axis = .horizontal) -> UIView {
let spacer = UIView()
if layout == .horizontal {
let constraint = spacer.widthAnchor.constraint(equalToConstant: size)
constraint.priority = .defaultLow
constraint.isActive = true
} else {
let constraint = spacer.heightAnchor.constraint(equalToConstant: size)
constraint.priority = .defaultLow
constraint.isActive = true
}
return spacer
}
}
Side note:
You may get an error that
This NSLayoutConstraint is being configured with a constant that
exceeds internal limits
... so I would rather recommend creating a UIView as a container and then pin each label to the sides of the container (leading, trailing) where each label also has a widthAnchor (if you don't want the text to extend).

What can cause UIStackView subviews to collapse?

I'm programmatically building a vertical UIStackView to show 3 short UILabel's:
import UIKit
import PlaygroundSupport
let box = UIView(frame: CGRect(origin: CGPoint(x:0, y:0), size: CGSize(width: 100, height: 100)))
box.backgroundColor = .white
let labels: [UILabel] = (1...3).map{ index in
let label = UILabel()
label.text = "item \(index)"
return label
}
let sv = UIStackView(arrangedSubviews: labels)
sv.axis = .vertical
sv.alignment = .center
sv.translatesAutoresizingMaskIntoConstraints = false
box.addSubview(sv)
sv.centerXAnchor.constraint(equalTo: box.centerXAnchor).isActive = true
sv.centerYAnchor.constraint(equalTo: box.centerYAnchor).isActive = true
// performing this font change on a single item collapses the stack view
if let label = sv.subviews[1] as? UILabel {
label.font = UIFont.italicSystemFont(ofSize: UIFont.labelFontSize)
}
PlaygroundPage.current.liveView = box
Performing font change of a single label from the stack view as shown above makes the labels collapse onto each other turning into unreadable blob of letters:
Instead of
I get
Am I missing some basic required constraint?
I should mention I'm using Xcode 10.2.1 Playground to develop this view.

how to create a zoom transition using custom navigation transition?

I'm new to navigation transition. I'm using Swift language for development.
What i'm trying to do is, whenever user click on the tableview cell the new view will open and it look like it coming from the image view of the cell.
I created a class of UIViewControllerAnimatedTransitioning.
class ThumbnailZoomTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private let duration: TimeInterval = 0.5
var operation: UINavigationControllerOperation = .push
var thumbnailFrame = CGRect.zero
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let presenting = operation == .push
// Determine which is the master view and which is the detail view that we're navigating to and from. The container view will house the views for transition animation.
let containerView = transitionContext.containerView
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { return }
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else { return }
let storyFeedView = presenting ? fromView : toView
let storyDetailView = presenting ? toView : fromView
// Determine the starting frame of the detail view for the animation. When we're presenting, the detail view will grow out of the thumbnail frame. When we're dismissing, the detail view will shrink back into that same thumbnail frame.
var initialFrame = presenting ? thumbnailFrame : storyDetailView.frame
let finalFrame = presenting ? storyDetailView.frame : thumbnailFrame
// Resize the detail view to fit within the thumbnail's frame at the beginning of the push animation and at the end of the pop animation while maintaining it's inherent aspect ratio.
let initialFrameAspectRatio = initialFrame.width / initialFrame.height
let storyDetailAspectRatio = storyDetailView.frame.width / storyDetailView.frame.height
if initialFrameAspectRatio > storyDetailAspectRatio {
initialFrame.size = CGSize(width: initialFrame.height * storyDetailAspectRatio, height: initialFrame.height)
}
else {
initialFrame.size = CGSize(width: initialFrame.width, height: initialFrame.width / storyDetailAspectRatio)
}
let finalFrameAspectRatio = finalFrame.width / finalFrame.height
var resizedFinalFrame = finalFrame
if finalFrameAspectRatio > storyDetailAspectRatio {
resizedFinalFrame.size = CGSize(width: finalFrame.height * storyDetailAspectRatio, height: finalFrame.height)
}
else {
resizedFinalFrame.size = CGSize(width: finalFrame.width, height: finalFrame.width / storyDetailAspectRatio)
}
// Determine how much the detail view needs to grow or shrink.
let scaleFactor = resizedFinalFrame.width / initialFrame.width
let growScaleFactor = presenting ? scaleFactor: 1/scaleFactor
let shrinkScaleFactor = 1/growScaleFactor
if presenting {
// Shrink the detail view for the initial frame. The detail view will be scaled to CGAffineTransformIdentity below.
storyDetailView.transform = CGAffineTransform(scaleX: shrinkScaleFactor, y: shrinkScaleFactor)
storyDetailView.center = CGPoint(x: thumbnailFrame.midX, y: thumbnailFrame.midY)
storyDetailView.clipsToBounds = true
}
// Set the initial state of the alpha for the master and detail views so that we can fade them in and out during the animation.
storyDetailView.alpha = presenting ? 0 : 1
storyFeedView.alpha = presenting ? 1 : 0
// Add the view that we're transitioning to to the container view that houses the animation.
containerView.addSubview(toView)
containerView.bringSubview(toFront: storyDetailView)
// Animate the transition.
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: {
// Fade the master and detail views in and out.
storyDetailView.alpha = presenting ? 1 : 0
storyFeedView.alpha = presenting ? 0 : 1
if presenting {
// Scale the master view in parallel with the detail view (which will grow to its inherent size). The translation gives the appearance that the anchor point for the zoom is the center of the thumbnail frame.
let scale = CGAffineTransform(scaleX: growScaleFactor, y: growScaleFactor)
let translate = CGAffineTransform(translationX: storyFeedView.frame.midX - self.thumbnailFrame.midX, y: storyFeedView.frame.midY - self.thumbnailFrame.midY)
storyFeedView.transform = translate.concatenating(scale)
storyDetailView.transform = .identity
}
else {
// Return the master view to its inherent size and position and shrink the detail view.
storyFeedView.transform = .identity
storyDetailView.transform = CGAffineTransform(scaleX: shrinkScaleFactor, y: shrinkScaleFactor)
}
// Move the detail view to the final frame position.
storyDetailView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
}) { finished in
transitionContext.completeTransition(finished)
}
}
}
When i click on the cell then new controller open. This functionality is working correctly. But whenever i'm going back then my previous controller get disappear.
Is i'm doing something wrong here ,
// Animate the transition.
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: {
// Fade the master and detail views in and out.
storyDetailView.alpha = presenting ? 1 : 0
storyFeedView.alpha = presenting ? 0 : 1
if presenting {
// Scale the master view in parallel with the detail view (which will grow to its inherent size). The translation gives the appearance that the anchor point for the zoom is the center of the thumbnail frame.
let scale = CGAffineTransform(scaleX: growScaleFactor, y: growScaleFactor)
let translate = CGAffineTransform(translationX: storyFeedView.frame.midX - self.thumbnailFrame.midX, y: storyFeedView.frame.midY - self.thumbnailFrame.midY)
storyFeedView.transform = translate.concatenating(scale)
storyDetailView.transform = .identity
}
else {
// Return the master view to its inherent size and position and shrink the detail view.
storyFeedView.transform = .identity
storyDetailView.transform = CGAffineTransform(scaleX: shrinkScaleFactor, y: shrinkScaleFactor)
}
// Move the detail view to the final frame position.
storyDetailView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
}) { finished in
transitionContext.completeTransition(finished)
}
I'm trying to Scale the master view in parallel with the detail view (which will grow to its inherent size). The translation gives the appearance that the anchor point for the zoom is the center of the thumbnail frame.
It will give zoomIn ZoomOut effect when user go back to previous controller. But When i trying to do this then my previous controller disappear.
I wanted to do like zoom transition like apple do in their apps.

UIButton not visible in UIScrollView with UIPageControl - Swift 3

I am dynamically creating a scrollView and having a page control associated with it.I have an array of buttons and these buttons is being added to the scroll view dynamically.While the buttons thats are added in the first page are visible , some buttons are not visible , but they are being added to the Scroll View.
UIScrollView frame = CGRect(x: 0, y: 0, width: 340, height: 150)
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(page), height: scrollView.frame.size.height)
In my case , I have 2 pages , so the content width is 680.
On scrolling the view , the content offset of the scroll view is 340 , although the the buttons that are invisible are within the contentSize , the buttons are still not visible ,
Following are the frames of the button that are not visible ,
<UIButton: 0x7ffdf0f26bb0; frame = (355 10; 62 32); opaque = NO; layer = <CALayer: 0x610000030840>>,
<UIButton: 0x7ffdf0f27110; frame = (427 10; 98 32); opaque = NO; layer = <CALayer: 0x610000030960>>,
<UIButton: 0x7ffdf0f27670; frame = (535 10; 112 32); opaque = NO; layer = <CALayer: 0x610000030a80>>
As far as page control is concerned , even if i remove the page control , I see the same behavior in the scrollview.
I am not sure if i have to check for anything specific on the scrollview.
#IBAction func changePage(sender: UIPageControl) -> () {
let x = CGFloat(pageControl.currentPage + 1) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x: x,y :scrollView.frame.size.height), animated: true)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
The issues was not with the page control or scrollview , I was using an extension to round corners of the scrollView which was causing this issue ,
extension UIView {
func roundCorners(corners:UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}}
I still don't understand why adding mask to the layer was messing the scrollView.

UITextView in Swift2.2 shows Pixelated text, how do I regain the lost resolution?

Image of the pixelated text in the UITextView
Does anyone have any suggestions? The image of the issue is in the clickable link above.
Code:
struct Views {
static var name_field: UITextView?
}
In the viewDidLoad()
Views.name_field = UITextView(frame: CGRectMake(0, 0, name_field_width, 50))
Views.name_field!.textAlignment = NSTextAlignment.Center
Views.name_field!.font = UIFont.systemFontOfSize(15)
Views.name_field!.autocorrectionType = UITextAutocorrectionType.No
Views.name_field!.keyboardType = UIKeyboardType.Default
Views.name_field!.returnKeyType = .Done
Views.name_field!.delegate = self
Calling this function to style it
styleIt(Views.name_field!)
Adds a bottom border style and then sets the font, etc.
func styleIt(target: UITextView){
target.layer.backgroundColor = UIColor.clearColor().CGColor
let _border = CAShapeLayer()
_border.backgroundColor = UIColor.whiteColor().CGColor
_border.frame = CGRectMake(0, CGRectGetHeight(target.frame) - 1.0, CGRectGetWidth(target.frame), 1.0)
_border.shadowColor = UIColor.whiteColor().CGColor
_border.shadowOffset = CGSize(width: 3, height: 3)
_border.shadowOpacity = 0.23
_border.shadowRadius = 4
target.layer.addSublayer(_border)
target.font = UIFont(name: "ClementePDaa-Hairline", size: 24)
target.textColor = UIColor.whiteColor()
target.textContainerInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
applyPlaceholderStyle(target, placeholderText: _SEARCH_TEXT)
target.returnKeyType = .Done
target.frame = CGRectIntegral(target.frame)
target.layer.shouldRasterize = true
_border.shouldRasterize = true
target.textInputView.layer.shouldRasterize = true
}
This UITextView is a subview of search_field which is simply a UIView
search_field!.addSubview(Views.name_field!)
Your text view is blurry because the frame is using floating numbers.
To force integers value for your frame just do :
textView.frame = CGRectIntegral(textView.frame)

Resources