iOS: NSMutableAttributedString.AddAttributes not rendered. (Xamarin) - ios

I am trying to change the style of a link in a UILabel in Xamarin.iOS.
I wrote the following code and the NSMutableAttributedString is updated correctly.
However the updated style is not rendered.
Am I missing something?
I am testing on the iOS Simulator.
mutableHtmlString.EnumerateAttribute(linkAttributeName, new NSRange(0, mutableHtmlString.Length), NSAttributedStringEnumeration.LongestEffectiveRangeNotRequired,
(NSObject value, NSRange range, ref bool stop) =>
{
var attrHyperlink = new UIStringAttributes
{
UnderlineStyle = NSUnderlineStyle.None,
ForegroundColor = UIColor.Red,
};
if (value != null && value is NSUrl url)
{
mutableHtmlString.AddAttributes(attrHyperlink, range);
System.Diagnostics.Debug.WriteLine(#$"XXX: {mutableHtmlString}");
}
});
control.AttributedText = mutableHtmlString;
NSUnderlineStyle.PatternDash is rendered correctly.
Also KerningAdjustment and UnderlineColor.
Is this limitation of UILabel?

From Apple: To promote consistency, the intended behavior is for ranges that represent links (specified via NSLinkAttributeName) to be drawn using the default link appearance. So the current behavior is the expected behavior.
So this mean it's an UILabel limitation and it's intentional.
However, there is a workaround. Just replace NSLink with a custom attribute.
mutableHtmlString.EnumerateAttribute(linkAttributeName, new NSRange(0, mutableHtmlString.Length), NSAttributedStringEnumeration.LongestEffectiveRangeNotRequired,
(NSObject value, NSRange range, ref bool stop) =>
{
var attrHyperlink = new UIStringAttributes
{
UnderlineStyle = NSUnderlineStyle.None,
ForegroundColor = UIColor.Red,
};
if (value != null && value is NSUrl url)
{
mutableHtmlString.AddAttribute(customAttributeName, value, range);
mutableHtmlString.RemoveAttribute("NSLink", range);
mutableHtmlString.AddAttributes(attrHyperlink, range);
}
});
control.AttributedText = mutableHtmlString;
The you can use customAttributeName to find the string if needed.
Thanks to https://exceptionshub.com/color-attribute-is-ignored-in-nsattributedstring-with-nslinkattributename.html

Related

How to properly set DataStore for storing boolean value

I want to set a simple switch that'll save a boolean value and if then block in my function.
Currently I have this in my DataStore:
companion object {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("userToken")
private val AutoRestartSystemUI = booleanPreferencesKey("AutoRestartSystemUIValue")
}
var getAutoRestartSystemUIValue: Flow<Boolean> = context.dataStore.data.map { preferences ->
(preferences[AutoRestartSystemUI] ?: "") as Boolean
}
suspend fun setAutoRestartSystemUI(value: Boolean) {
context.dataStore.edit { preferences ->
preferences[AutoRestartSystemUI] = value
}
}
}
and
Button(onClick = {
// if [pro.themed.manager.UserStore(context).getAutoRestartSystemUIValue = true] ()
CoroutineScope(Dispatchers.IO).launch {
UserStore(context).setAutoRestartSystemUI(false)
}
}) {
Text(text = UserStore(context).getAutoRestartSystemUIValue.collectAsState(
initial = ""
).toString())
}
in my activity. I have generally no idea of what I should do next and for some weird reason instead of showing value in a text (temp solution for testing) i have
How do i simplify datastore? How do I properly implement switch that'll make it = !it? How to set default value?

How to capture/extract text value from a text field in EarlGrey?

I'm using EarlGrey framework and I want to select the whole text and capture it from the textfield or search bar. Is there a way to do it?
Thanks in advance!
You extracted the value with XCTest APIs method.
The right way can be found in EarlGrey FAQ
https://github.com/google/EarlGrey/blob/master/docs/faq.md
// Swift
//
// Must use a wrapper class to force pass by reference in Swift 3 closures.
// inout params cannot be modified within closures. http://stackoverflow.com/a/28252105
open class Element {
var text = ""
}
/*
* Example Usage:
*
* let element = Element()
* domainField.performAction(grey_replaceText("hello.there"))
* .performAction(grey_getText(element))
*
* GREYAssertTrue(element.text != "", reason: "get text failed")
*/
public func grey_getText(_ elementCopy: Element) -> GREYActionBlock {
return GREYActionBlock.action(withName: "get text",
constraints: grey_respondsToSelector(#selector(getter: UILabel.text))) { element,
errorOrNil -> Bool in
let elementObject = element as? NSObject
let text = elementObject?.perform(#selector(getter: UILabel.text),
with: nil)?.takeRetainedValue() as? String
elementCopy.text = text ?? ""
return true
}
}
Finally, found out !
Using application.navigationBars["Navigation bar Name"].searchFields["Label of the textField / Search Bar"].value
This fetched the value from the textfield. One thing to be noted is that, the value retrieved will be of the type "any"
Can anyone write the below code with objective-c?
// Swift
//
// Must use a wrapper class to force pass by reference in Swift 3 closures.
// inout params cannot be modified within closures. http://stackoverflow.com/a/28252105
open class Element {
var text = ""
}
/*
* Example Usage:
*
* let element = Element()
* domainField.performAction(grey_replaceText("hello.there"))
* .performAction(grey_getText(element))
*
* GREYAssertTrue(element.text != "", reason: "get text failed")
*/
public func grey_getText(_ elementCopy: Element) -> GREYActionBlock {
return GREYActionBlock.action(withName: "get text",
constraints: grey_respondsToSelector(#selector(getter: UILabel.text))) { element,
errorOrNil -> Bool in
let elementObject = element as? NSObject
let text = elementObject?.perform(#selector(getter: UILabel.text),
with: nil)?.takeRetainedValue() as? String
elementCopy.text = text ?? ""
return true
}
}

SWT: Integrate clickable link into StyledText

With the help of this question I was able to figure out how I can display a link inside a StyledText widget in SwT. The color is correct and even the cursor changes shape when hovering over the link.
So far so good, but the link is not actually clickable. Although the cursor changes its shape, nothing happens if clicking on the link. Therefore I am asking how I can make clicking the link to actually open it in the browser.
I thought of using a MouseListener, tracking the click-location back to the respective text the click has been performed on and then deciding whether to open the link or not. However that seems way too complicated given that there already is some routine going on for changing the cursor accordingly. I believe that there is some easy way to do this (and assuring that the clicking-behavior is actually consistent to when the cursor changes its shape).
Does anyone have any suggestions?
Here's an MWE demonstrating what I have done so far:
public static void main(String[] args) throws MalformedURLException {
final URL testURL = new URL("https://stackoverflow.com/questions/1494337/can-html-style-links-be-added-to-swt-styledtext");
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, true));
StyledText sTextWidget = new StyledText(shell, SWT.READ_ONLY);
final String firstPart = "Some text before ";
String msg = firstPart + testURL.toString() + " some text after";
sTextWidget.setText(msg);
sTextWidget.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
StyleRange linkStyleRange = new StyleRange(firstPart.length(), testURL.toString().length(), null, null);
linkStyleRange.underline = true;
linkStyleRange.underlineStyle = SWT.UNDERLINE_LINK;
linkStyleRange.data = testURL.toString();
sTextWidget.setStyleRange(linkStyleRange);
shell.open();
while(!shell.isDisposed()) {
display.readAndDispatch();
}
}
Okay I was being a little too fast on posting this question... There's a snippet that deals with exactly this problem and it shows, that one indeed has to use an extra MouseListener in order to get things working.
The snippet can be found here and this is the relevant part setting up the listener:
styledText.addListener(SWT.MouseDown, event -> {
// It is up to the application to determine when and how a link should be activated.
// In this snippet links are activated on mouse down when the control key is held down
if ((event.stateMask & SWT.MOD1) != 0) {
int offset = styledText.getOffsetAtLocation(new Point (event.x, event.y));
if (offset != -1) {
StyleRange style1 = null;
try {
style1 = styledText.getStyleRangeAtOffset(offset);
} catch (IllegalArgumentException e) {
// no character under event.x, event.y
}
if (style1 != null && style1.underline && style1.underlineStyle == SWT.UNDERLINE_LINK) {
System.out.println("Click on a Link");
}
}
}
});

UITextViewL link on xamarin.ios

I have a custom UITextView and need something like this:
Google
By the way, This is my Initialize code on custom class:
void Initialize()
{
Font = UIFont.FromName("Lacuna Regular", 14f);
Editable = false;
DataDetectorTypes = UIDataDetectorType.Link;
Text = "Google";
}
but I don't know how to write the Url where I need to go (in this case, www.google.es).
Thanks in advance!
Via UIDataDetectorType.Links:
uiview.DataDetectorTypes = UIDataDetectorType.Link;
uiview.Text = #"https://www.google.es";
Via NSAttributedStringDocumentAttributes with NSAttributedString:
var urlString = #"Google";
var documentAttributes = new NSAttributedStringDocumentAttributes { DocumentType = NSDocumentType.HTML };
NSError error = null;
var attributedString = new NSAttributedString(NSData.FromString(urlString, NSStringEncoding.UTF8), documentAttributes, ref error);
// Should really check the NSError before applying
uiview.AttributedText = attributedString;

Default values from settings bundle are not loaded in Xamarin.iOS

In my project I have defined a settings.bundle containing a Root.plist with several settings, that all have default values.
However on first start on a new device these defaults are shown in the settings app, but not loaded.
What's going wrong here?
It turns out, that this intended. The DefaultValue specification in Settings.bundle serves only display purposes. Found on ijure.org
There you also find a solution in Objective C to get the default values and write them to the settings dictionary if a value is not present already.
I rewrote it with inspiration from this answer to a similar question:
private static void RegisterDefaultsFromSettingsBundle()
{
var defaults = NSUserDefaults.StandardUserDefaults;
defaults.Synchronize();
var settingsBundle = NSBundle.MainBundle.PathForResource(#"Settings", #"bundle");
if (string.IsNullOrEmpty(settingsBundle))
{
Console.WriteLine("Could not find Settings.bundle!");
return;
}
var settings = NSDictionary.FromFile(settingsBundle + #"/Root.plist");
var preferences = settings[(NSString)"PreferenceSpecifiers"] as NSArray;
using (var defaultsToRegister = new NSMutableDictionary())
{
if (preferences != null)
{
foreach (var prefItem in NSArray.FromArray<NSDictionary>(preferences))
{
var key = prefItem[(NSString) "Key"] as NSString;
if (key != null)
{
var currentObject = defaults[key];
if (currentObject == null)
{
// Not yet set in the defaults
var defaultValue = prefItem[#"DefaultValue"];
defaultsToRegister.Add(key, defaultValue);
Console.WriteLine($"Setting value '{defaultValue}' for key '{key}'");
}
else
{
// Already set in the defaults: don't touch
Console.WriteLine($"Key '{key}' is readable (value: '{currentObject}'), nothing written to defaults.");
}
}
}
}
defaults.RegisterDefaults(defaultsToRegister);
}
defaults.Synchronize();
}
Hope this helps someone

Resources