change UIPicker height programmatically - swift

I would like to modify the height of UIPicker programmatically, I tried to do this but on the simulator the height remains as it was before, I don't see any changes.
#objc open func showPicker(title: String?, selected: String?, strings:[String], color: UIColor? = nil, completion:DPPickerValueIndexCompletion?) {
self.pickerValues = strings
let picker = UIPickerView()
picker.delegate = self
picker.dataSource = self
picker.transform = CGAffineTransformMakeScale(0.5, 0.5)
if let value = selected {
picker.reloadAllComponents()
if strings.count > 0 {
OperationQueue.current?.addOperation {
let index = strings.firstIndex(of: value) ?? 0
picker.selectRow(index, inComponent: 0, animated: false)
}
}
}
self.showPicker(title: title, view: picker, color: color) { (cancel) in
var index = -1
var value: String? = nil
if !cancel, strings.count > 0 {
index = picker.selectedRow(inComponent: 0)
if index >= 0 {
value = self.pickerValues?[index]
}
}
completion?(value, index, cancel || index < 0)
}
}

I don't why it's not resizing itself properly when you translate autoresizing mask into constraints, but it works properly when you use AutoLayout:
let pickerView = UIPickerView()
pickerView.translatesAutoresizingMaskIntoConstraints = false
pickerView.delegate = self
pickerView.dataSource = self
view.addSubview(pickerView)
NSLayoutConstraint.activate([
pickerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
pickerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
pickerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
pickerView.heightAnchor.constraint(equalToConstant: 500)
])

Related

How to colour radio buttons?

You can see that Apple has coloured the radio buttons. I would like to do the same. I can't seems to find the option to change the colour in Interface Builder on storyboard.
As for doing it, programmatically, I tried enabling layer .wantsLayer = true and then tried to set the colour by .layer?.borderColour = NSColor.systemBlue.cgColor and tried .layer?.backgroundColor = NSColor.systemRed.cgColor and other similar properties but no avail.
Likewise, how do you add the colour rectangles on NSMenuItem on NSPopUpButton?
The following code demonstrates a group of custom radio buttons for MacOS made by subclassing NSButton. It may be run in an Xcode swift project by copy/pasting into a newly added file called ‘main.swift’ and deleting the original AppDelegate.
import Cocoa
class CustomButton: NSButton {
var circleColor: NSColor!
override func draw(_ rect: NSRect) {
let circle = NSBezierPath(ovalIn: bounds)
switch(self.tag) {
case 0:
circleColor = NSColor.red
case 1:
circleColor = NSColor.green
case 2:
circleColor = NSColor.yellow
case 3:
circleColor = NSColor.orange
default:
break
}
circleColor.set()
circle.fill()
if(self.state) == .on {
let dotRect = NSInsetRect(bounds, 18.0, 18.0);
let dot = NSBezierPath (ovalIn:dotRect)
let dotColor = NSColor.black
dotColor.set()
dot.fill()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
#objc func radioGrpAction(_ sender:NSButton) {
print("You selected: id = \(sender.tag)")
}
func buildMenu() {
let mainMenu = NSMenu()
NSApp.mainMenu = mainMenu
// **** App menu **** //
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu()
appMenuItem.submenu = appMenu
appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q")
}
func buildWnd() {
let _wndW : CGFloat = 300
let _wndH : CGFloat = 200
window = NSWindow(contentRect:NSMakeRect(0,0,_wndW,_wndH),styleMask:[.titled, .closable, .miniaturizable], backing:.buffered, defer:false)
window.center()
window.title = "Radio Button Group"
window.makeKeyAndOrderFront(window)
// === Radio Grp Box === //
let grpBox = NSBox(frame: NSMakeRect( 50,_wndH - 100, 150, 60))
grpBox.title = "Radio Group"
window.contentView!.addSubview (grpBox)
// === Radio Horizontal Grid === //
let _btnW : CGFloat = 24
let _btnH : CGFloat = 24
let _left : CGFloat = 10 // left margin first button
let _YOffset : CGFloat = 5 // 0,0 at left, bottom of group box
let _spacing : CGFloat = 5 // spacing between buttons
for x in stride(from:0, through:3, by:1) {
let _XOffset = _left + CGFloat(x)*(_btnW + _spacing)
let btn = CustomButton(frame:NSMakeRect(_XOffset, _YOffset, _btnW, _btnH))
btn.setButtonType(.radio)
btn.tag = x
if(x == 0){btn.state = .on}
btn.action = #selector(self.radioGrpAction(_:))
grpBox.contentView!.addSubview(btn)
}
// === Quit btn === //
let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
quitBtn.bezelStyle = .circular
quitBtn.title = "Q"
quitBtn.action = #selector(NSApplication.terminate)
window.contentView!.addSubview(quitBtn)
}
func applicationDidFinishLaunching(_ notification: Notification) {
buildMenu()
buildWnd()
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}
let appDelegate = AppDelegate()
// ***** main.swift ***** //
let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.delegate = appDelegate
app.activate(ignoringOtherApps:true)
app.run()
Ok the same but for AppKit:
import Cocoa
import AppKit
extension NSView {
func centerX(inView view: NSView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: NSView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
}
class CustomRadioButton: NSView {
private let containerSize: CGFloat = 60.0
private let selectorSize: CGFloat = 20.0
var containerColor: NSColor = .blue
var selectorColor: NSColor = .red
var selected: Bool = true {
didSet {
selectorView.isHidden = !selected
}
}
private lazy var containerView: NSView = {
let view = NSView()
view.wantsLayer = true
view.layer?.backgroundColor = containerColor.cgColor
return view
}()
private lazy var selectorView: NSView = {
let view = NSView()
view.wantsLayer = true
view.layer?.backgroundColor = selectorColor.cgColor
return view
}()
override init(frame: CGRect) {
super.init(frame: CGRect(x: 0, y: 0, width: containerSize, height: containerSize))
configureUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configureUI() {
addSubview(containerView)
containerView.setDimensions(height: containerSize, width: containerSize)
containerView.layer?.cornerRadius = containerSize / 2
containerView.centerX(inView: self)
containerView.centerY(inView: self)
addSubview(selectorView)
selectorView.setDimensions(height: selectorSize, width: selectorSize)
selectorView.layer?.cornerRadius = selectorSize / 2
selectorView.centerY(inView: containerView)
selectorView.centerX(inView: containerView)
}
}
let selector = CustomRadioButton()
selector.selected = false

I want to make an if statement that each image would be full screen when the user is tap

here is the code that I check if they images are tapped but only the 3 image is apply full screen. As you can see I set the image is tapped to statusImageView to call the function of zoomImage So I want fixed for all images.
func imageTapped() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageZoom(tapGestureRecognizer:)))
if detailsImage[0].tag == 0 {
detailsImage[0].isUserInteractionEnabled = true
detailsImage[0].addGestureRecognizer(tapGestureRecognizer)
self.statusImageView = detailsImage[0]
}
let tapGestureRecognizer1 = UITapGestureRecognizer(target: self, action: #selector(imageZoom(tapGestureRecognizer:)))
detailsImage[1].isUserInteractionEnabled = true
detailsImage[1].addGestureRecognizer(tapGestureRecognizer1)
self.statusImageView = detailsImage[1]
let tapGestureRecognizer2 = UITapGestureRecognizer(target: self, action: #selector(imageZoom(tapGestureRecognizer:)))
detailsImage[2].isUserInteractionEnabled = true
detailsImage[2].addGestureRecognizer(tapGestureRecognizer2)
self.statusImageView = detailsImage[2]
}
let blackBackgroundColor = UIView()
let tappedImage = UIImageView()
var statusImageView: UIImageView?
let navigationBarView = UIView()
#objc func imageZoom(tapGestureRecognizer: UITapGestureRecognizer) {
annimationImage(detailsImage: statusImageView!)
}
And then I call the annihilationImage function which is takes the statusImageView as input
func annimationImage(detailsImage: UIImageView) {
if let startingFrame = statusImageView?.superview?.convert(statusImageView!.frame, to: nil) {
statusImageView!.alpha = 0
blackBackgroundColor.frame = self.view.frame
blackBackgroundColor.backgroundColor = UIColor.black
blackBackgroundColor.alpha = 0
view.addSubview(blackBackgroundColor)
navigationBarView.frame = CGRect(x: 0, y: 0, width: 1000, height: 100)
navigationBarView.backgroundColor = UIColor.black
navigationBarView.alpha = 0
if let keyWindow = UIApplication.shared.keyWindow {
keyWindow.addSubview(navigationBarView)
}
let tappedImage = UIImageView()
tappedImage.backgroundColor = .gray
tappedImage.frame = startingFrame
tappedImage.contentMode = .scaleToFill
tappedImage.isUserInteractionEnabled = true
tappedImage.image = statusImageView?.image
tappedImage.clipsToBounds = true
view.addSubview(tappedImage)
UIView.animate(withDuration: 0.75, animations: {
tappedImage.frame = CGRect(x: 0, y: y, width: self.view.frame.size.width, height: 300)
})
}
}
I want to make an if statement that while check which image is tapped
You can use tapGestureRecognizer.view for getting a view that is assigned a tap gesture. And by this, you can also get the view tag (your view is corresponding to the image view in your code)
#objc func imageZoom(tapGestureRecognizer: UITapGestureRecognizer) {
let senderView = tapGestureRecognizer.view // Here you get a view that is associated with the gesture
let tag = senderView?.tag // Here you get a tag that is assigned to all image
// Add your condition here by tag
annimationImage(detailsImage: statusImageView!)
}

How can I animate this tableview header Y position change

I want to slide my table view header into view if the use scrolls up my feed and the header is off screen. If the user then scrolls down I want to hide it again.
I believe I have this working using the following:
final class AccountSettingsController: UITableViewController {
let items: [Int] = Array(0...500)
private lazy var menuView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemPink
view.heightAnchor.constraint(equalToConstant: 120).isActive = true
view.widthAnchor.constraint(equalToConstant: 100).isActive = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = []
extendedLayoutIncludesOpaqueBars = false
tableView.tableHeaderViewWithAutolayout = menuView
tableView.tableFooterView = .init()
tableView.refreshControl = .init()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "This is setting #\(indexPath.row)"
return cell
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.y
let transY = scrollView.panGestureRecognizer.translation(in: scrollView).y
if scrollView.contentOffset.y > 120 {
if transY > 0 {
menuView.transform = .init(translationX: 0, y: contentOffset)
} else if transY < 0 {
menuView.transform = .identity
}
return
}
if scrollView.contentOffset.y <= 120 {
if transY < 0 {
menuView.transform = .identity
} else if transY > 0 {
menuView.transform = .init(translationX: 0, y: contentOffset)
}
}
}
}
I would like to animate the slide in / slide out of the header, so it slides down and up, rather than just appears.
I tried to add UIView.animate blocks such as
if scrollView.contentOffset.y <= 120 {
if transY < 0 {
menuView.transform = .identity
} else if transY > 0 {
UIView.animate(withDuration: 0.25, animations: {
self.menuView.transform = .init(translationX: 0, y: contentOffset)
})
}
}
but this produces a random jumping effect on the header and does not achieve what I would like at all.
I've attached a gif that shows the current effect, as you can see it just appears and disappears. I'd like this to animate down and up for for show / hide.
I also have an extension to set the correct size for my table view header, which you can find here -
extension UITableView {
var tableHeaderViewWithAutolayout: UIView? {
set (view) {
tableHeaderView = view
if let view = view {
lowerPriorities(view)
view.frame.size = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
tableHeaderView = view
}
}
get {
return tableHeaderView
}
}
fileprivate func lowerPriorities(_ view: UIView) {
for cons in view.constraints {
if cons.priority.rawValue == 1000 {
cons.priority = UILayoutPriority(rawValue: 999)
}
for v in view.subviews {
lowerPriorities(v)
}
}
}
}
I agree with Claudio, using the tableHeaderView is probably complicating things as it is not really intended to be manipulated in this way.
Try the below and see if this gets you what you are looking for.
Instead of manipulating the offset of the header, manipulate the height. You can still give the impression of a scroll.
You could also introduce scrollView.panGestureRecognizer.velocity(in: scrollView) if you wanted to apply a threshold so the menu appears only after a particular scroll speed.
class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let data: [Int] = Array(0...99)
private let menuView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .purple
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var tableView: UITableView = {
let view = UITableView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.dataSource = self
view.delegate = self
// view.refreshControl = .init()
view.contentInsetAdjustmentBehavior = .never
view.tableFooterView = .init()
return view
}()
var menuViewHeightAnchor: NSLayoutConstraint!
var tableViewTopAnchor: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
menuViewHeightAnchor = menuView.heightAnchor.constraint(equalToConstant: 120)
tableViewTopAnchor = tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 120)
edgesForExtendedLayout = []
[tableView, menuView].forEach(view.addSubview)
NSLayoutConstraint.activate([
menuViewHeightAnchor,
menuView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
menuView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
menuView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableViewTopAnchor,
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(frame: .zero)
cell.textLabel?.text = "Cell #\(indexPath.row)"
return cell
}
private var previousOffsetY: CGFloat = 0
private var velocityThreshold: CGFloat = 500
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = min(120, max(0, scrollView.contentOffset.y))
let translation = scrollView.panGestureRecognizer.translation(in: scrollView)
let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
let offsetYDiff = previousOffsetY - offsetY
previousOffsetY = offsetY
let adjustedOffset = menuViewHeightAnchor.constant + offsetYDiff
tableViewTopAnchor.constant = 120 - max(0, offsetY)
menuViewHeightAnchor.constant = min(120, adjustedOffset)
guard offsetY == 120 else { return }
if translation.y > 0 { // DRAGGED DOWN
UIView.animate(withDuration: 0.33, animations: {
self.menuViewHeightAnchor.constant = 120
self.view.layoutIfNeeded()
})
} else if translation.y < 0 { // DRAGGED UP
UIView.animate(withDuration: 0.33, animations: {
self.menuViewHeightAnchor.constant = 0
self.view.layoutIfNeeded()
})
}
}
}
I believe the best approach is by setting the menuView as a subview of the tableViewController, for example on the viewDidLoad method and also adding an inset on the tableView like this:
let menuHeight: CGFloat = 120
var scrollStartingYPoint: CGFloat = 0
private lazy var menuView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemPink
view.heightAnchor.constraint(equalToConstant: menuHeight).isActive = true
view.widthAnchor.constraint(equalToConstant: 100).isActive = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(menuView)
NSLayoutConstraint.activate([
menuView.leftAnchor.constraint(equalTo: view.leftAnchor),
menuView.topAnchor.constraint(equalTo: view.topAnchor)
])
edgesForExtendedLayout = []
extendedLayoutIncludesOpaqueBars = false
tableView.contentInset.top = menuHeight
}
Then you can add the scrolling logic to show/hide the menu, I've done it a little different than you using the scrollViewWillBeginDragging method to store the initial scroll offset, to then determine the scroll direction and offset:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
scrollStartingYPoint = scrollView.contentOffset.y
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let tableYScrollOffset = scrollView.contentOffset.y
let offset = abs(tableYScrollOffset - scrollStartingYPoint)
if tableYScrollOffset < scrollStartingYPoint { // scrolling down
if menuView.frame.origin.y >= 0 {
return
}
var translationY = -menuHeight + offset
if translationY > 0 {
translationY = 0
}
menuView.transform = CGAffineTransform(translationX: 0, y: translationY)
} else { // scrolling up
if menuView.frame.origin.y <= -menuHeight {
return
}
var translationY = -offset
if translationY < -menuHeight {
translationY = -menuHeight
}
menuView.transform = CGAffineTransform(translationX: 0, y: translationY)
}
}
If you want to animate the presentation of the menu, then you can change the previous code and use your animation code like this:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let tableYScrollOffset = scrollView.contentOffset.y
let offset = abs(tableYScrollOffset - scrollStartingYPoint)
if tableYScrollOffset < scrollStartingYPoint { // scrolling down
UIView.animate(withDuration: 0.25, animations: {
self.menuView.transform = .identity
})
} else { // scrolling up
if menuView.frame.origin.y <= -menuHeight {
return
}
var translationY = -offset
if translationY < -menuHeight {
translationY = -menuHeight
}
menuView.transform = CGAffineTransform(translationX: 0, y: translationY)
}
}

Hide view item of NSStackView with animation

I working with swift 4 for macOS and I would like to hide an stack view item with animation.
I tried this:
class ViewController: NSViewController {
#IBOutlet weak var box: NSBox!
#IBOutlet weak var stack: NSStackView!
var x = 0
#IBAction func action(_ sender: Any) {
if x == 0 {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
self.stack.arrangedSubviews.last!.isHidden = true
self.view.layoutSubtreeIfNeeded()
x = 1
}, completionHandler: nil)
} else {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
self.stack.arrangedSubviews.last!.isHidden = false
self.view.layoutSubtreeIfNeeded()
x = 0
}, completionHandler: nil)
}
}
}
The result will be:
It works!
But I am not happy with the animation style.
My wish is:
I press the button, the red view will be smaller to the right side
I press the button, the red view will be larger to the left side.
Like a sidebar or if you have an splitview controller and you will do splitviewItem.animator().isCollapsed = true
this animation of show/hide is perfect.
Is this wish possible?
UPDATE
self.stack.arrangedSubviews.last!.animator().frame = NSZeroRect
UPDATE 2
self.stack.arrangedSubviews.last!.animator().frame = NSRect(x: self.stack.arrangedSubviews.last!.frame.origin.x, y: self.stack.arrangedSubviews.last!.frame.origin.y, width: 0, height: self.stack.arrangedSubviews.last!.frame.size.height)
I just create a simple testing code which can animate the red view, instead of using button, I just used touchup, please have a look at the code:
class ViewController: NSViewController {
let view1 = NSView()
let view2 = NSView()
let view3 = NSView()
var x = 0
var constraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view1.wantsLayer = true
view2.wantsLayer = true
view3.wantsLayer = true
view1.layer?.backgroundColor = NSColor.orange.cgColor
view2.layer?.backgroundColor = NSColor.green.cgColor
view3.layer?.backgroundColor = NSColor.red.cgColor
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
view3.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view1)
self.view.addSubview(view2)
self.view.addSubview(view3)
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view1]|", options: [], metrics: nil, views: ["view1": view1]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view2]|", options: [], metrics: nil, views: ["view2": view2]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view3]|", options: [], metrics: nil, views: ["view3": view3]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view1(==view2)][view2(==view1)][view3]|", options: [], metrics: nil, views: ["view1": view1, "view2": view2, "view3": view3]))
constraint = NSLayoutConstraint(item: view3, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
self.view.addConstraint(constraint)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
override func mouseUp(with event: NSEvent) {
if x == 0 {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
constraint.constant = 0
self.view.layoutSubtreeIfNeeded()
x = 1
}, completionHandler: nil)
} else {
NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
constraint.constant = 100
self.view.layoutSubtreeIfNeeded()
x = 0
}, completionHandler: nil)
}
}
}
Have a look at Organize Your User Interface with a Stack View sample code.
I disliked the heightContraint on the view controller and the height calculation in the -viewDidLoad. The code below calculation the size before animating and only adds the contrain while animating.
Breakable Contraint
First you need to set the priority of the contraint to something lower than 1000 (required constraint) for the direction you want the animation. Here the bottom contraint.
While we animate the view in and out, we will add contraint to do so with a priority of 1000 (required constraint).
Code
#interface NSStackView (LOAnimation)
- (void)lo_toggleArrangedSubview:(NSView *)view;
#end
#implementation NSStackView (LOAnimation)
- (void)lo_toggleArrangedSubview:(NSView *)view
{
NSAssert([self detachesHiddenViews], #"toggleArrangedSubview requires detachesHiddenViews YES");
NSAssert([[self arrangedSubviews] containsObject:view], #"view not an arrangedSubview");
CGFloat postAnimationHeight = 0;
NSLayoutConstraint *animationContraint = nil;
if (view.hidden) {
view.hidden = NO; // NSStackView will re-add view to its subviews
[self layoutSubtreeIfNeeded]; // calucalte view size
postAnimationHeight = view.bounds.size.height;
animationContraint = [view.heightAnchor constraintEqualToConstant:0];
animationContraint.active = YES;
} else {
[self layoutSubtreeIfNeeded]; // calucalte view size
animationContraint = [view.heightAnchor constraintEqualToConstant:view.bounds.size.height];
animationContraint.active = YES;
}
[self layoutSubtreeIfNeeded]; // layout with animationContraint in place
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animationContraint.animator.constant = postAnimationHeight;
[self layoutSubtreeIfNeeded];
} completionHandler:^{
view.animator.hidden = (postAnimationHeight == 0);
[view removeConstraint:animationContraint];
}];
}
#end

Can the transition from the selection UITextField press to another UITextField cause the UIView to reload in Swift?

I have the following view as soon as my app loads:
As soon as I click on the Full name text field, the keyboard shows and shifts the elements a little bit to allow the register button stays appearing.
The problem appears when I move to the second text field. It causes the view to reload entirely and place all the elements in the view to their original place which makes the key board to hide the lowest elements. which is shown in the next figure
I have searched and checked if the problem comes from the fact that the keyboard loads keyboardwillshow() and keyboardwillhide do not get called at all when this problem happens as I have verified.
Can the transition from the selection UITextField press to another UITextField cause the UIView to reload Xcode? Please not that the UIText fields are in a
import UIKit
import SendBirdSDK
class LoginController: UIViewController {
var keyboardShowing = false
let inputsContainerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.white
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 5
view.layer.masksToBounds = true
return view
}()
lazy var loginRegisterButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor(r: 88, g: 101, b: 161)
button.setTitle("Register", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(UIColor.white, for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
button.addTarget(self, action: #selector(handleLoginRegister), for: .touchUpInside)
return button
}()
func handleLoginRegister() {
if loginRegisterSegmentedControl.selectedSegmentIndex == 0 {
handleLogin()
} else {
handleRegister()
}
}
func handleLogin(){
guard let userid = fullNameTextField.text, (passwordTextField.text != nil) else {
print ("Form is not valid")
return
}
SBDMain.connect(withUserId: userid, completionHandler: { (user, error) in
if error != nil {
print (error)
return
}
//successfully loggedin user
self.dismiss(animated: true, completion: nil)
})
}
let nameTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "Full Name"
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let nameSeperatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(r: 220, g: 220, b: 220)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let fullNameTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "Username"
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let fullNameSeperatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(r: 220, g: 220, b: 220)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let passwordTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "Password"
tf.translatesAutoresizingMaskIntoConstraints = false
tf.isSecureTextEntry = true
return tf
}()
lazy var profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "gameofthrones_splash")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSelectProfileImageView)))
imageView.isUserInteractionEnabled = true
return imageView
}()
lazy var loginRegisterSegmentedControl: UISegmentedControl = {
let sc = UISegmentedControl(items: ["Login", "Register"])
sc.translatesAutoresizingMaskIntoConstraints = false
sc.tintColor = UIColor.white
sc.selectedSegmentIndex = 1
sc.addTarget(self, action: #selector(handleLoginRegisterChange), for: .valueChanged)
return sc
}()
func handleLoginRegisterChange() {
if keyboardShowing == true {
view.endEditing(true)
}
/*else {
view.endEditing(false)
}*/
let title = loginRegisterSegmentedControl.titleForSegment(at: loginRegisterSegmentedControl.selectedSegmentIndex)
loginRegisterButton.setTitle(title, for: UIControlState())
// change height of inputContainerView, but how???
inputsContainerViewHeightAnchor?.constant = loginRegisterSegmentedControl.selectedSegmentIndex == 0 ? 100 : 150
// change height of nameTextField
nameTextFieldHeightAnchor?.isActive = false
nameTextFieldHeightAnchor = nameTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: loginRegisterSegmentedControl.selectedSegmentIndex == 0 ? 0 : 1/3)
nameTextFieldHeightAnchor?.isActive = true
nameTextField.isHidden = loginRegisterSegmentedControl.selectedSegmentIndex == 0
fullNameTextFieldHeightAnchor?.isActive = false
fullNameTextFieldHeightAnchor = fullNameTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: loginRegisterSegmentedControl.selectedSegmentIndex == 0 ? 1/2 : 1/3)
fullNameTextFieldHeightAnchor?.isActive = true
passwordTextFieldHeightAnchor?.isActive = false
passwordTextFieldHeightAnchor = passwordTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: loginRegisterSegmentedControl.selectedSegmentIndex == 0 ? 1/2 : 1/3)
passwordTextFieldHeightAnchor?.isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(r: 61, g: 91, b: 151)
view.addSubview(inputsContainerView)
view.addSubview(loginRegisterButton)
view.addSubview(profileImageView)
view.addSubview(loginRegisterSegmentedControl)
setupInputsContainerView()
setupLoginRegisterButton()
setupProfileImageView()
setupLoginRegisterSegmentedControl()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
//connecting to the application
SBDMain.initWithApplicationId("1662A8E8-F45F-454B-9E5E-02362342ECC5")
}
func keyboardWillShow(notification: NSNotification) {
print ("keyboardwillshow was called")
if self.keyboardShowing {
return
}
if self.keyboardShowing == false {
self.inputsContainerView.frame.origin.y -= 58
self.loginRegisterButton.frame.origin.y -= 58
self.loginRegisterSegmentedControl.frame.origin.y -= 58
self.profileImageView.frame.origin.y -= 58
}
self.keyboardShowing = true
}
func keyboardWillHide(notification: NSNotification) {
print ("keyboardwillhide was called")
if !self.keyboardShowing {
return
}
if self.keyboardShowing == true {
self.inputsContainerView.frame.origin.y += 58
self.loginRegisterButton.frame.origin.y += 58
self.loginRegisterSegmentedControl.frame.origin.y += 58
self.profileImageView.frame.origin.y += 58
}
self.keyboardShowing = false
}
func setupLoginRegisterSegmentedControl() {
//need x, y, width, height constraints
loginRegisterSegmentedControl.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginRegisterSegmentedControl.bottomAnchor.constraint(equalTo: inputsContainerView.topAnchor, constant: -12).isActive = true
loginRegisterSegmentedControl.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor, multiplier: 1).isActive = true
loginRegisterSegmentedControl.heightAnchor.constraint(equalToConstant: 36).isActive = true
}
func setupProfileImageView(){
//need x, y, width, height constraints
profileImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
profileImageView.bottomAnchor.constraint(equalTo: loginRegisterSegmentedControl.topAnchor, constant: -12).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 150).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 150).isActive = true
}
var inputsContainerViewHeightAnchor: NSLayoutConstraint?
var nameTextFieldHeightAnchor: NSLayoutConstraint?
var fullNameTextFieldHeightAnchor: NSLayoutConstraint?
var passwordTextFieldHeightAnchor: NSLayoutConstraint?
func setupInputsContainerView(){
//need x, y, width, height constraints
inputsContainerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
inputsContainerView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
print(self.inputsContainerView.frame.origin.y)
inputsContainerView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -24).isActive = true
inputsContainerViewHeightAnchor = inputsContainerView.heightAnchor.constraint(equalToConstant: 150)
inputsContainerViewHeightAnchor?.isActive = true
inputsContainerView.addSubview(nameTextField)
inputsContainerView.addSubview(nameSeperatorView)
inputsContainerView.addSubview(fullNameTextField)
inputsContainerView.addSubview(fullNameSeperatorView)
inputsContainerView.addSubview(passwordTextField)
//need x, y, width, height constraints
nameTextField.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: 12).isActive = true
nameTextField.topAnchor.constraint(equalTo: inputsContainerView.topAnchor).isActive = true
nameTextField.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
nameTextFieldHeightAnchor = nameTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/3)
nameTextFieldHeightAnchor?.isActive = true
//need x, y, width, height constraints
nameSeperatorView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
nameSeperatorView.topAnchor.constraint(equalTo: nameTextField.bottomAnchor).isActive = true
nameSeperatorView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
nameSeperatorView.heightAnchor.constraint(equalToConstant: 1).isActive = true
//need x, y, width, height constraints
fullNameTextField.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: 12).isActive = true
fullNameTextField.topAnchor.constraint(equalTo: nameTextField.bottomAnchor).isActive = true
fullNameTextField.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
fullNameTextFieldHeightAnchor = fullNameTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/3)
fullNameTextFieldHeightAnchor?.isActive = true
//need x, y, width, height constraints
fullNameSeperatorView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
fullNameSeperatorView.topAnchor.constraint(equalTo: fullNameTextField.bottomAnchor).isActive = true
fullNameSeperatorView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
fullNameSeperatorView.heightAnchor.constraint(equalToConstant: 1).isActive = true
//need x, y, width, height constraints
passwordTextField.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: 12).isActive = true
passwordTextField.topAnchor.constraint(equalTo: fullNameTextField.bottomAnchor).isActive = true
passwordTextField.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
passwordTextFieldHeightAnchor = passwordTextField.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/3)
passwordTextFieldHeightAnchor?.isActive = true
}
func setupLoginRegisterButton(){
//need x, y, width, height constraints
loginRegisterButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginRegisterButton.topAnchor.constraint(equalTo: inputsContainerView.bottomAnchor, constant: 12).isActive = true
loginRegisterButton.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
loginRegisterButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
extension UIColor {
convenience init(r: CGFloat, g: CGFloat, b: CGFloat ) {
self.init(red: r/255, green: g/255, blue: b/255, alpha: 1)
}
}
Instead of moving the elements of the UIView move the whole view frame like this:
func keyboardWillShow(notification: NSNotification) {
print ("keyboardwillshow was called")
if self.keyboardShowing {
return
}
if self.keyboardShowing == false {
/*self.inputsContainerView.frame.origin.y -= 58
self.loginRegisterButton.frame.origin.y -= 58
self.loginRegisterSegmentedControl.frame.origin.y -= 58
self.profileImageView.frame.origin.y -= 58*/
self.view.frame.origin.y -= 58
}
self.keyboardShowing = true
}
func keyboardWillHide(notification: NSNotification) {
print ("keyboardwillhide was called")
if !self.keyboardShowing {
return
}
if self.keyboardShowing == true {
/* self.inputsContainerView.frame.origin.y += 58
self.loginRegisterButton.frame.origin.y += 58
self.loginRegisterSegmentedControl.frame.origin.y += 58
self.profileImageView.frame.origin.y += 58*/
self.view.frame.origin.y += 58
}
self.keyboardShowing = false
}