Why Is Asserting Resolved UIColor Failing After Changing UIUserInterfaceStyle in Unit Test? - uikit

I am working on a project that uses named color assets, and I am responsible for updating the unit tests that assert the correct color values.
We have an XCTestCase subclass (BaseXCTestCase) that has a window property, which is set to a value of UIApplication.shared.firstKeyWindow:
var window = UIApplication.shared.firstKeyWindow!
firstKeyWindow is defined as windows.filter { $0.isKeyWindow }.first in an UIApplication extension:
var firstKeyWindow: UIWindow? {
return windows.filter { $0.isKeyWindow }.first
}
Additionally, there are two properties used for accessing window's traitCollection and userInterfaceStyle:
var traitCollection: UITraitCollection {
return window.traitCollection
}
var userInterfaceStyle: UIUserInterfaceStyle {
get {
return window.traitCollection.userInterfaceStyle
}
set {
window.overrideUserInterfaceStyle = newValue
}
}
The class that contains the color tests inherits from BaseXCTestCase. Then, our tests are configured as:
func testColor() {
var subject = .namedColor
/// Light mode
userInterfaceStyle = .light
subject = subject.resolvedColor(with: traitCollection)
XCTAssertEqual(subject.hexString(), "#ABC123")
XCTAssertEqual(subject.alpha, 1.0)
/// Dark mode
userInterfaceStyle = .dark
subject = subject.resolvedColor(with: traitCollection)
XCTAssertEqual(subject.hexString(), "#BCD234")
XCTAssertEqual(subject.alpha, 1.0)
}
The first two assertions of the test succeed when asserting the color for light mode. However, the assertions for the dark mode color fail, and the failure messages indicate that the light mode color and alpha values are persisting.
I set a breakpoint at the final light-mode assertion, and printing traitCollection provides me with the following output, which I expect:
<UITraitCollection: 0x60000198a760;
UserInterfaceIdiom = Phone,
DisplayScale = 3,
DisplayGamut = P3,
HorizontalSizeClass = Compact,
VerticalSizeClass = Regular,
UserInterfaceStyle = Light,
UserInterfaceLayoutDirection = LTR,
ForceTouchCapability = Unavailable,
PreferredContentSizeCategory = L,
AccessibilityContrast = Normal,
UserInterfaceLevel = Base
>
In the printed output, I see UserInterfaceStyle = Light.
Then, I set a breakpoint at the final dark-mode assertion, and printing traitCollection provides me with the following output, which I expect:
<UITraitCollection: 0x600001990460;
UserInterfaceIdiom = Phone,
DisplayScale = 3,
DisplayGamut = P3,
HorizontalSizeClass = Compact,
VerticalSizeClass = Regular,
UserInterfaceStyle = Dark,
UserInterfaceLayoutDirection = LTR,
ForceTouchCapability = Unavailable,
PreferredContentSizeCategory = L,
AccessibilityContrast = Normal,
UserInterfaceLevel = Base
>
This time, I see UserInterfaceStyle = Dark, which I expect.
If I remove the code that sets the interface style to light and sets the resolved color, then the test passes:
func testColor() {
var subject = .namedColor
/// Light mode
XCTAssertEqual(subject.hexString(), "#ABC123")
XCTAssertEqual(subject.alpha, 1.0)
/// Dark mode
userInterfaceStyle = .dark
subject = subject.resolvedColor(with: traitCollection)
XCTAssertEqual(subject.hexString(), "#BCD234")
XCTAssertEqual(subject.alpha, 1.0)
}
My question is then, why are the assertions for the dark-mode color and alpha values failing? Specifically, what is causing the color to not be resolved after overriding window's overrideUserInterfaceStyle?
Shouldn't the following code cause the color to be resolved with the trait collection that contains UserInterfaceStyle = Dark as the dark-mode color and alpha values?
userInterfaceStyle = .dark
subject = subject.resolvedColor(with: traitCollection)
If I move the dark-mode logic above the light-mode logic, then the light-mode assertions fail. I don't know what I am failing to do between the first assertions and second assertions to ensure the user interface style is being respected.

I don't think unit tests are guaranteed to run on the main thread, so maybe try a DispatchQueue.main.async for the UI update and see how that goes.

Related

SwiftUI animation - toggled Boolean always ends up as true

I'm trying to create an animation in my app when a particular action happens which will essentially make the background of a given element change colour and back x number of times to create a kind of 'pulse' effect. The application itself is quite large, but I've managed to re-create the issue in a very basic app.
So the ContentView is as follows:
struct ContentView: View {
struct Constants {
static let animationDuration = 1.0
static let backgroundAlpha: CGFloat = 0.6
}
#State var isAnimating = false
#ObservedObject var viewModel = ContentViewViewModel()
private let animation = Animation.easeInOut(duration: Constants.animationDuration).repeatCount(6, autoreverses: false)
var body: some View {
VStack {
Text("Hello, world!")
.padding()
Button(action: {
animate()
}) {
Text("Button")
.foregroundColor(Color.white)
}
}
.background(isAnimating ? Color.red : Color.blue)
.onReceive(viewModel.$shouldAnimate, perform: { _ in
if viewModel.shouldAnimate {
withAnimation(self.animation, {
self.isAnimating.toggle()
})
}
})
}
func animate() {
self.viewModel.isNew = true
}
}
And then my viewModel is:
import Combine
import SwiftUI
class ContentViewViewModel: ObservableObject {
#Published var shouldAnimate = false
#Published var isNew = false
var cancellables = Set<AnyCancellable>()
init() {
$isNew
.sink { result in
if result {
self.shouldAnimate = true
}
}
.store(in: &cancellables)
}
}
So the logic I am following is that when the button is tapped, we set 'isNew' to true. This in turn is a publisher which, when set to true, sets 'shouldAnimate' to true. In the ContentView, when shouldAnimate is received and is true, we toggle the background colour of the VStack x number of times.
The reason I am using this 'shouldAnimate' published property is because in the actual app, there are several different actions which may need to trigger the animation, and so it feels simpler to have this tied to one variable which we can listen for in the ContentView.
So in the code above, we should be toggling the isAnimating bool 6 times. So, we start with false then toggle as follows:
1: true, 2: false, 3: true, 4: false, 5: true, 6: false
So I would expect to end up on false and therefore have the background white. However, this is what I am getting:
I tried changing the repeatCount (in case I was misunderstanding how the count works):
private let animation = Animation.easeInOut(duration: Constants.animationDuration).repeatCount(7, autoreverses: false)
And I get the following:
No matter the count, I always end on true.
Update:
I have now managed to get the effect I am looking for by using the following loop:
for i in 0...5 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i), execute: {
withAnimation(self.animation, {
self.isAnimating.toggle()
})
})
}
Not sure this is the best way to go though....
To understand what is going on, it would help to understand CALayer property animations.
When you define an animation the system captures the state of a Layer and watches for changes in the animatable properties of that layer. It records property changes for playback during the animation. To present the animation, it create a copy of the layer in its initial state (the presentationLayer). It then substitutes the copy in place of the actual layers on screen and runs the animation by manipulating the animatable properties of the presentation layer.
I this case, when you begin the animation, the system watches what happens to the CALayer that backs your view and captures the changes to any animatable properties (in this case the background color). It then creates a presentationLayer and replays those property changes repeatedly. It's not running your code repeatedly - it's changing the properties of the presentation Layer.
In other words the animation the system knows the layer's background color property should toggle back and forth because of the example you set in your animation block, but the animation toggles the background color back and forth without running your code again.

Show current frame rate of a MTKView

I know that in SceneKit, you can enable a banner on the side of the SKView to look at real time frame rates and other useful debugging information. But what about MTKView? I don't seem to find such a property to enable, or how I can query the current frame rate. (Because I am rendering something that have a frame rate of 0.5fps or so)
I don't think there is simple flag for you. Because you control the complete rendering pipeline when creating a command buffer, Metal can't know where to inject a rendering pass with some custom text.
You could inject your own rendering pass (based on a flag like var showDebugInformation = true) in your pipeline, but that sounds like a bit of work.
I would probably monitor frame times manually in the draw method and update a label every draw. A rough outline could look like this:
var previousFrameAtTime: Date
let lastFrameTime = CurrentValueSubject<TimeInterval, Never>(.infinity)
func draw(in view: MTKView) {
lastFrameTime.send(Date().timeIntervalSince(previousFrameAtTime))
previousFrameAtTime = Date()
// ...
}
Then you can observe this value in your view, something like this:
import Combine
class MyViewController: UIViewController {
let label = UILabel()
var cancellables: [AnyCancellable] = []
func subscribeToFrameTime() {
renderer.lastFrameTime
.sink { label.text = "\($0 * 1000) ms." }
.store(in: &cancellables)
}
}

ID Buffer THREE JS WEBGL Non Photorealistic Rendering Edge Detection

I'm working on some screen space shaders using THREE.JS and GLSL. I'd like to both render a "diffuse" buffer with per object materials/textures AND render an "ID" buffer where each object has a unique color for object edge detection. This would also be really useful for object picking as some of the THREE.JS examples show.
I am already using Scene.overrideMaterial to render normal buffers (and some other cool stuff) but haven't figured out a way to do something similar for an ID buffer. Is there something like this that exists? Seems useful enough that it would be likely. Have other people developed solutions to this problem? Maybe by mapping an objects UUID to a color value?
-----> EDIT
Here is my attempt using this example:
https://threejs.org/examples/webgl_interactive_instances_gpu.html
I'm not concerned with selection at this point, regardless this isn't working. I think I may be setting up onBeforeRender() incorrectly. Any help on this?
Thanks!
initPieces = function(geometry){
...
//set id Color to unique color
//for picking will use setHex(i) method
m.userData.idColor = material.color;
...
m.onBeforeRender = function (){
if(Viewer._scene.overrideMaterial){
if(Viewer._scene.overrideMaterial._name == "id"){
var updateList = [];
var u = scene.overrideMaterial.uniforms;
//picking color in user data
var d = this.userData;
//Is this just equivalent to checking whether this is the picking material?
if(u.idColor){
u.idColor.value = (d.idColor);
// u.needsUpdate = true;
updateList.push("idColor");
}
//Don't understand what this is doing
//Throws "WebGL INVALID_OPERATION: uniformMatrix4fv: location is not from current program"
if (updateList.length){
var materialProperties = renderer.properties.get(material);
if( materialProperties.program){
var gl = renderer.getContext();
var p = materialProperties.program;
gl.useProgram( p.program );
var pu = p.getUniforms();
updateList.forEach(function(name){
pu.setValue( gl, name, u[name].value);
});
}
}
}
}
}
// Doesn't show onBeforeRender set as I expected?
console.log(m);
...
}
...
}
...
function render(scene, camera){
...
scene.overrideMaterial = getMaterial("id");
renderer.render(scene, camera, getTarget("id"));
scene.overrideMaterial = null;
...
}

Add Image dynamically in runtime - Unity 5

In my unity based Android game I wish to add the image dynamically based on the number of questions in each level. The image is shown for reference. Each correct answer will be marked in green and the wrong one in red. I am new to unity and trying hard to find steps to achieve this.
Any help with an example for this requirement will be a great help.
I once wrote a script for dynamically creating buttons based on each level. What I did was creating the first button on the scene and adding the other buttons based on the first one. Below is the shell of my code:
// tutorialButton and levelButtons are public variables which can be set from Inspector
RectTransform rect = tutorialButton.GetComponent<RectTransform> ();
for (int i = 1; i < levelSize; i++) {
// Instantiate the button dynamically
GameObject newButton = GameObject.Instantiate (tutorialButton);
// Set the parent of the new button (In my case, the parent of tutorialButton)
newButton.transform.SetParent (levelButtons.transform);
//Set the scale to be the same as the tutorialButton
newButton.transform.localScale = tutorialButton.transform.localScale;
//Set the position to the right of the tutorialButton
Vector3 position = tutorialButton.transform.localPosition;
position.x += rect.rect.width*i;
newButton.transform.localPosition = position;
}
I am not exactly sure if this is the right approach as it may or may not give unexpected results depending on different screen sizes and your canvas, but hopefully it gives you an idea about dynamically creating objects.
I'm not sure if this helps, but if you have all the images in the scene under a canvas, with this you just need to drag the canvas on the script and use
//level-1 is to keep the array notation
FindObjectOfType<NameOfScript>.ChangeColor(level-1,Color.green);
or you can do also
//level-1 is to keep the array notation
FindObjectOfType<NameOfScript>.RevertColor(level - 1);
This is the script:
//Keep it private but you still see it in inspector
//#Encapsulation :)
[SerializeField]
private Canvas _canvas;
private Image[] _images;
//keep the original colors in case you want to change back
private Color[] _origColors;
void Start () {
_images = GetComponentsInChildren<Image>();
_origColors = new Color[_images.Length];
for (int i = 0; i < _images.Length; i++)
{
_origColors[i] = _images[i].color;
}
}
//Reverts the color of the image back to the original
public void RevertToOriginal(int imageIndex)
{
_images[imageIndex].color = _origColors[imageIndex];
}
//Change to color to the coresponding index, starts from 0
public void ChangeColor(int imageIndex, Color color)
{
_images[imageIndex].color = color;
}
P.S If you want it visible only at the end you can make a method where you enable = (true or false) for the canvas. So you keep it false till the end of the level and you make it true when you want to show, while after every answer you call the ChangeColor depending on the result.
To make it easier you can use:
NameOfScript variableName = FindObjectOfType<NameOfScript>();
and after that you just call
variableName.ChangeColor(level - 1, Color.green);
Also it does not matter where you put the script. I would make some kind of manager(empty GameObject) in the scene and put it there.

Changing alpha of Text's CanvasGroup in OnTriggerEnter Unity

I'm currently working on a game that pays hommage to Marble Blast Gold/Ultra.
At this point I have text that is positioned with the marble. The text is a child of a canvas and I have a canvas group added to the text. I initially set the alpha of the canvas group to 0 so that you can't see the text.
What I'm trying to do is have it so that when you pick up a power up the text reappears by changing the canvas group's alpha back to 1 and then once the power up is used set it back to 0.
I'm not seeming to have any luck with my current code.
// Super Jump pickup
if (col.gameObject.tag == "Spring")
{
superJumpText.GetComponent<CanvasGroup>().alpha = 1;
canSuperJump = true;
canSuperSpeed = false;
col.gameObject.SetActive(false);
hitSuperJump = true;
Invoke("Display", 20);
}
void Update()
{
//super jump
if (canSuperJump)
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Vector3 jump = new Vector3(0, superJumpForce, 0);
GetComponent<Rigidbody>().AddForce(jump);
canSuperJump = false;
superJumpText.GetComponent<CanvasGroup>().alpha = 0;
}
}
}
The only reason this would not work for you is that somewhere in your parent hierarchy you have a gameobject with another canvas group. That would ALWAYS override the child canvas group settings UNLESS you select the 'Ignore Parent Groups' checkbox in the child canvas

Resources