Dynamically assigned UIFont does not show immediately in slow simulator - swift

I have a UIView with a UILabel for which, I'd like allow the user to dynamically change the font size. The code below almost works, but the problem is that after the new UIFont is assigned to the label, it doesn't immediately show in the UI. Instead, some other UI activity is required to make it show up.
What I'm looking for is a technique where the font change will show up immediately after the gesture is over.
Starting with the technique shown in this tutorial, I added a gesture recognizer to the UIScrollView. In the associated UIViewController, I implemented handlePinch(UIpinchGestureRecognizer).
Inside that function, I take action only when the gesture begins and ends (no attempt to animate). If the user indicated zoom, I increase the font by 4 points. If the user indicated a shrink, I decrease the font by 4 points. This all works, except for the fact that the screen does not reflect the change until another UI function is executed. I have only tested this on the simulator, but I presume it would be the same on a real device.
I have tried setNeedsDisplay and setNeedsLayout and several other odd things on both the UILabel as well as the view that was passed to the handlePinch() function.
#IBAction func handlePinch(_ gesture: UIPinchGestureRecognizer) {
guard let gestureView = gesture.view else {
return;
}
gestureView.transform = gestureView.transform.scaledBy(x: gesture.scale, y: gesture.scale)
if (gesture.state == UIPinchGestureRecognizer.State.began) {
pinchD = gestureView.transform.d
print("pinch started ", pinchD)
}
if (gesture.state == UIPinchGestureRecognizer.State.ended) {
print("pinch ended ", gestureView.transform.d)
print("compare:", pinchD, gestureView.transform.d)
let larger = gestureView.transform.d > pinchD
let fontSize = multiLineLabel.font.pointSize
if (larger) {
if (fontSize < 28) {
let calculatedFont = UIFont(name: multiLineLabel.font.fontName, size: fontSize + 4)
print("bigger fontSize", fontSize + 4);
multiLineLabel.font = calculatedFont
}
} else {
if (fontSize > 12) {
let calculatedFont = UIFont(name: multiLineLabel.font.fontName, size: fontSize - 4)
print("smaller fontSize", fontSize - 4);
multiLineLabel.font = calculatedFont
}
}
}
}
What needs to be done after the calculatedFont is applied to the UILabel to make it show immediately in the app?
ADDITIONAL CLARIFICATION:
handlePinch() runs as expected; every time a pinch gesture ends, I see bigger fontSize... or smaller fontSize... logged.
After the logging, the simulator screen font size remains the same until I do one of several things. One thing that causes the font size to change is if I swipe to the next item. Before the animation starts, the font increases. The font changes also if I hit the back button (the font change shows briefly before the other view appears).

It turns out that my presumption was incorrect:
I have only tested this on the simulator, but I presume it would be the same on a real device.
I believe that was NOT correct.
I'm testing on a very, very old iMac and the simulator is bogged down such that it takes 15 or 30 seconds to update the screen!
So I conclude that the code is working fine after all.

Related

How to adjust position of CAShapeLayer based upon device size?

I'm attempting to create a CAShapeLayer animation that draws an outline around the frame of a UILabel. Here's the code:
func newQuestionOutline() -> CAShapeLayer {
let outlineShape = CAShapeLayer()
outlineShape.isHidden = false
let circularPath = UIBezierPath(roundedRect: questionLabel.frame, cornerRadius: 5)
outlineShape.path = circularPath.cgPath
outlineShape.fillColor = UIColor.clear.cgColor
outlineShape.strokeColor = UIColor.yellow.cgColor
outlineShape.lineWidth = 5
outlineShape.strokeEnd = 0
view.layer.addSublayer(outlineShape)
return outlineShape
}
func newQuestionAnimation() {
let outlineAnimation = CABasicAnimation(keyPath: "strokeEnd")
outlineAnimation.toValue = 1
outlineAnimation.duration = 5
newQuestionOutline().add(outlineAnimation, forKey: "key")
}
The animation performs as expected when running on the simulator for an iPhone 11 which is the device size that I used in the storyboard. However when running the project on a different device with different screen dimensions (like iPhone 8 plus) the shape is drawn out of place and not around the UILabel as it should be. I used autolayout to horizontally and vertically center the UILabel to the center of the view so the UILabel is centered no matter what device.
Any suggestions? Thanks in advance!
Cheers!
A shape layer is not a view, so it is not subject to auto layout. And any time you say something like roundedRect: questionLabel.frame you are making yourself dependent on what questionLabel.frame is at that moment, which is a huge mistake because that is exactly what is not determined until auto layout determines what the frame will be (and can change later if auto layout changes its mind due to changing conditions, such as rotation etc.)
There are two kinds of solution:
Host the shape layer in a view. Now you have something that is subject to autolayout. You will still need to redraw the shape layer whenever the view changes its frame, but you can detect that and perform the redraw.
Implement your view controller's viewDidLayoutSubviews to detect that auto layout has just done its work. Respond by (for example) removing the shape layer and making a new one based on the current conditions.

NSButton not masking to bounds

I am transferring an app for iOS to a toolbar app in OSX. Although I am using swift, the objects are rather different. I am having one major problem, however, that I cannot seem to overcome.
The code below produces the NSButton shown, but I cannot get rid of the grey background. masktobound has no effect. I have tried masking each corner individually, no effect. I just want a simple round button.
I also have several buttons with rounded corners, however these also show the light grey background.
Any pointers? Sample code would be appreciated.
let connectButton = NSButton.init(title: NSLocalizedString(" ", comment: "OnButtonAccessibility"), target: self, action: #selector(toggle))
connectButton.wantsLayer = true
connectButton.isBordered = false
connectButton.layer?.masksToBounds=true
if #available(OSX 10.13, *) {
connectButton.layer?.maskedCorners=[.layerMaxXMaxYCorner]
} else {
connectButton.layer?.backgroundColor = NSColor.blue.cgColor
connectButton.layer?.cornerRadius=80
connectButton.layer?.borderColor=DarkerBlue.cgColor
connectButton.layer?.borderWidth=3
(connectButton.cell as! NSButtonCell).isBordered=false
(connectButton.cell as! NSButtonCell).backgroundColor=NSColor.clear
connectButton.isTransparent=true
connectButton.frame=CGRect(x: 80, y: self.view.frame.size.height-260, width: 160, height: 160)
connectButton.tag=1002
self.view.addSubview(connectButton)
It appears that you must at least touch the NSButton.cell to have it conform to the button style. The backgroundstyle used seems to make no difference is you have the button filled or containing an image.
connectButton.cell?.backgroundStyle=NSView.BackgroundStyle.dark

How to determine if a cell has scrolled off screen XCUI

I have a XCUI test case in swift where I am trying to determine if a cell has scrolled off screen. However, I've noticed that once a cell has been on screen the static text is always findable, even when the cell scrolls off screen, when using
XCTAssertTrue(app.tables.cells.StaticText["person"].exists)
This also does not work for me
let window = app.windows.elementBoundByIndex(0)
let element = app.tables.cells.staticTexts["person"]
XCTAssertTrue(CGRectContainsRect(window.frame, element.frame))
As that second test will pass, even when the cell has scrolled off screen.
Is there a way to determine whether a table cell is no longer within the view on the screen?
Use the hittable API on XCUIElement to determine if an element both exists and is on screen. You should use it on the cell.
Note: hittable will become isHittable in Swift 3.
let cell = app.tables.cells.containingType(.StaticText, identifier: "person").elementBoundByIndex(0)
XCTAssertTrue(cell.hittable)
A solution to this problem, kind of clunky but works, is that I used a function like this
func tapOnSpecifiedPointOnList(cellNumber: Float) {
let cellSpacing: Float = 75
let xCoordinate: Float = 25
let yOffSet: Float = 90
let yCoordinate = yOffSet + (cellSpacing * cellNumber)
let pointToTap = CGPointMake(CGFloat(xCoordinate), CGFloat(yCoordinate))
map().tapAtPosition(pointToTap)
}
tapOnSpecifiedPointOnList(0)
let startingCell = app.cells.otherElements["callout"].elementBoundByIndex(0).label
app.cells.otherElements["callout"].swipeUp()
for i in 0...numberOfCells {
tapOnSpecifiedPointOnList(i)
let nextCell = app.cells.otherElements["callout"].elementBoundByIndex(0).label
XCTAssertNotEqual(startingCell, nextCell)
}
When a cell was tapped, it opened up a separate callout, which didn't exist before. By attaching an accessibilityId on this callout object, I was able to grab the info from the cell and compare it to other cells which I tapped on. Therefore, if I tapped on all possible cell locations on screen, and none of callouts matched the original, it must have gone offscreen.

UIScreenEdgeRecognizerGesture smooth like safari

My container view controller has a screen edge pan gesture to change the views. The code for panning the views looks as follows:
func changeView(recognizer: UIScreenEdgePanGestureRecognizer) {
println("INITIAL: \(recognizer.translationInView(view))")
if recognizer.state == .Began {
// Create and configure the view
println("BEGAN: \(recognizer.translationInView(view))")
}
if recognizer.state == .Changed {
println("CHANGED: \(recognizer.translationInView(view))")
let translation = recognizer.translationInView(view)
currentView.view.center.x += translation.x
pendingView.view.center.x += translation.x
recognizer.setTranslation(CGPointZero, inView: view)
}
if recognizer.state == .Ended {
if recognizer.view!.center.x > view.bounds.size.width {
// Animate the view to position
} else {
// Animate the view back to original
}
}
}
While this works, I'm still having an issue with the start of the panning. When a user swipes quickly, translation will have a value big enough to make the start of the pan looking "unsmoothly".
For example, with a quick swipe translation will start with a value of 100. The value is then added to the center.x of the views causing the undesired effect.
I noticed Safari has a screen edge gesture as well to change views and this effect doesn't occur no matter how quick the swipe is. Nor does this happen with a normal UIPanGestureRecognizer.
I've tried wrapping the "animation" in UIView.animateWithDuration(). It does look more smooth, but then it feels it's just lagging behind the actual gesture, unlike how it's done in Safari.
Can someone please tell me a better way to pan the views so it will look as smooth as in Safari?
EDIT:
I've added several lines to check the value of the translation and the problem is it jumps from 0 to some value causing the unwanted behavior. It doesn't matter where I put recognizer.setTranslation(CGPointZero, inView: view).
The output is:
INITIAL: (21.5, 0.0)
BEGAN: (21.5, 0.0)
INITIAL: (188.0, -3.0)
CHANGED: (188.0, -3.0)
After some more testing:
func changeView(recognizer: UIScreenEdgePanGestureRecognizer) {
println("INITIAL: \(recognizer.translationInView(view))")
recognizer.setTranslation(CGPointZero, inView: view)
}
INITIAL: (0.0, 0.0)
INITIAL: (130.5, -35.5)
FINAL:
Seems like creating and preparing the new view is causing some kind of minor lag in Began. The small amount of lag is enough to create a difference in translation of 100-200.
Probably have to preload the views somewhere else I guess.
This won't solve all your problems, since, as you have rightly said, a screen edge pan gesture recognizer is a little crusty in its behavior; but do note that you are omitting one valuable piece of data - the question of what recognizer.translationInView is in the .Began state. At that time, obviously, the finger has already moved considerably; for, if it had not, we would not have recognized this as a screen edge pan gesture! You will thus be much happier, I think, if you construct your tests like this:
switch recognizer.state {
case .Began:
// ... do initial setup
fallthrough // <-- TAKE NOTE
case .Changed:
// respond to changes
default:break
}
In that way, you will capture the missing datum and respond to it, and the jump will not be quite so bad.
I tried logging in both began and changed and my numbers (showing translationInView with no setTranslation back to zero) are this sort of thing:
began
changed
(-16.5, 0.0)
changed
(-41.5, 0.0)
changed
(-41.5, 0.0)
changed
(-58.5, 0.0)
(The first one, preceded by began, is the fallthrough execution of changed.) So yes, we do go from nothing to -41 very fast, but at least there is an intermediate value of -16.5 so it isn't quite so abrupt.
Also I should add that if there is a serious delay and jump it may well be that you have multiple conflicting gesture recognizers. If so, you can detect this fact by using delegate methods such as gestureRecognizer:shouldRequireFailureOfGestureRecognizer: - which will also let you prioritize between them and perhaps make the other g.r. give way sooner.

Textfield cannot be moved/is stuck on UIView

I have been having an issue with a textfield within my app for about the last month and I haven't been able to figure out how to fix it. It hasn't been too much of an issue up until this point, but now that I am at the final stages of my app, it is causing real problems.
There are a few issues:
With auto-layout on:
No matter where I place the textfield within my view, when the app is run it jumps to x = 0, y = 0 underneath the navigation bar. I've tried moving it back to where it should be programatically, however this didn't work.
With auto-layout off:
Although with auto-layout off the textfield appears where it should be, I still can't move it anywhere as it seems to be stuck. This has become an issue now as I have just implemented code to move my UIView when a the user needs to type so that the textfield is not hidden.
This is how I am moving my UIView when the keyboard appears:
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
//let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
var frame = self.budgetEntryView.frame
frame.origin.y = frame.origin.y - keyboardSize.height + 167
self.budgetEntryView.frame = frame
}
}
Every UI element moves including other textfields however the problem textfield stays where it is.
Initially I thought the below code was the issue as if I remember correctly, removing it seemed to fix the issue. However I have just tried this again and the problem persists.
func textFieldDidChange(textField: UITextField) {
var text = textField.text.stringByReplacingOccurrencesOfString(currencyFormatter.currencySymbol, withString: "").stringByReplacingOccurrencesOfString(currencyFormatter.groupingSeparator, withString: "").stringByReplacingOccurrencesOfString(currencyFormatter.decimalSeparator, withString: "")
textField.text = currencyFormatter.stringFromNumber((text as NSString).doubleValue / 100.0)
currencyDouble = (text as NSString).doubleValue / 100.0
println(currencyDouble)
valueEnter.alpha = 100
}
If anyone has any idea what the issue is then I would be very grateful because I have been completely stumped for the last month and I can't make any more progress until it is fixed.
Note: I created the textfield via storyboard.
I finally figured out a solution. I had to recreate the textfield programatically. The textfield is now movable with or without auto layout on.